Skip to content

test: support Pi 1.0.1 in the Calm renderer contract - #38

Merged
cloud-practitioner merged 1 commit into
mainfrom
fm/fm-calm-pi-1-0-1-port
Oct 5, 2026
Merged

cloud-practitioner merged 1 commit into
mainfrom
fm/fm-calm-pi-1-0-1-port

Conversation

@cloud-practitioner

Copy link
Copy Markdown
Owner

Intent

firstmate-maint: b1.

That answer picks this option: copy upstream's calm-mode Pi test fix from kunchenguid#6512 ("test: support Pi 1.0.1 in the Calm renderer contract", by mremond) into this fork, credited to that PR in the commit and the PR, as a small PR. The problem it fixes: the CI test "Pi calm renderer and lifecycle contract" (tests/fm-calm-pi-extension.test.sh, in the "Behavior portable serial 6" job) fails since the CI runners moved to Pi 1.0.1 - Pi's HTML renderer now expects getToolRenderers while the test fixture supplies getToolDefinition, so the test reports "grep disappeared from /export calm.html HTML while calm mode was on". Keep the change to that test/renderer compatibility, matching upstream PR 6512. No check waivers.

Substance of upstream PR 6512: update both Calm HTML-renderer fixtures in tests/fm-calm-pi-extension.test.sh to supply getToolRenderers (Pi 1.0.1) alongside the older getToolDefinition hook through one shared registered-tool lookup, preserving existing assertions; and document the Pi 1.0.1 and 1.0.0 verification evidence and refresh command in docs/calm-mode-feasibility.md with a cross-link from docs/verification/runtime-backends.md.

What Changed

  • Update both Calm HTML-renderer fixtures to supply getToolRenderers alongside the older getToolDefinition hook through one shared registered-tool lookup, preserving existing assertions.
  • Document Pi 1.0.1 and 1.0.0 verification evidence and the refresh command in docs/calm-mode-feasibility.md, with a cross-link from docs/verification/runtime-backends.md.
  • Port the compatibility fix from upstream firstmate PR #6512 by @mremond; no production extension changes.

Risk Assessment

✅ Low: The change is narrowly scoped to test-fixture compatibility and verification documentation, supports both renderer APIs through one lookup, and leaves production behavior unchanged.

Testing

The targeted contract reproduced the exact pre-fix Pi 1.0.1 failure and passed after the fix on both Pi versions, including its /share renderer-trigger assertion. Real isolated CLI exports and remapped-submit checks passed on both versions; Chromium verified the rendered outputs and captured screenshots. Setup issues were resolved locally, all fixtures were removed, and no full suite or linters were run.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
Export a Calm session on Pi 1.0.1 without losing grep, find, or watcher HTML ✅ pass live pi-101-calm-export.html, pi-101-calm-export.png, pi-101-rendered-output.json; targeted baseline and target contract logs
Export the same Calm session on Pi 1.0.0 while retaining older renderer compatibility ✅ pass live pi-100-calm-export.html, pi-100-calm-export.png, pi-100-rendered-output.json; contract-current-pi-100.log
Remap submit to Alt+s and prevent ordinary Enter from activating an export ✅ pass live pi-101-live-result.json, pi-100-live-result.json, and corresponding terminal transcripts
Evidence: Validation methods, results, setup recovery, and scope

Source: Validation methods, results, setup recovery, and scope

# Calm renderer compatibility validation

Target: `104b70f1309fed605e42120963503528bd9d70be`
Baseline: `93d82355e4c37dbe4b91aba8d4e16f2789e7938e`

## Targeted regression check

Executed only the existing `test_rendering_and_session_lifecycle` function from `tests/fm-calm-pi-extension.test.sh`, retaining its definitions and assertions unchanged. Temporary selector scripts were created in `tests/` so the existing relative helper paths remained correct, then removed after validation. `TMPDIR` and `HOME` pointed inside the disposable workspace.

| Fixture revision | Real npm Pi version | Result |
|---|---|---|
| Baseline | 1.0.1 | Expected regression reproduced: `grep disappeared from /export calm.html HTML while calm mode was on` |
| Target | 1.0.1 | Passed |
| Baseline | 1.0.0 | Passed |
| Target | 1.0.0 | Passed |

The focused check includes the existing `/export`, remapped non-submit, `/share` renderer-trigger, session-choice persistence, stock-renderer restoration, and execution-data preservation assertions. This is a fixture-driven contract check, **not** evidence of a live hosted `/share` upload.

Initial use of the machine's managed Pi 1.0.0 package failed because its flattened dependency layout does not contain the nested `pi-tui` path required by this existing test. Retried successfully using disposable npm global-prefix installations entirely under the worktree for both versions; the machine installation was unchanged.

## Live CLI and browser evidence

Executed the real Pi 1.0.1 and 1.0.0 CLIs against separate disposable saved-session data, with the production Calm and watcher extensions copied unchanged. No provider request, watcher lifecycle operation, real account, shared fleet state, or hosted upload was used. The fixture data includes saved grep, find and watcher results; the product, extension loader, terminal handler, export pipeline, HTML renderer, and HTML viewer are real.

For each version:

1. Started a 160-column / 44-row pseudo-terminal, sizing the slave before the CLI launched and continuously draining the master.
2. Loaded a Calm-on session, toggled `/calm` off and back on with Alt+s, and verified persisted choices.
3. Typed `/export <path>` and pressed Enter while submit was remapped to Alt+s. No export file appeared.
4. Pressed Alt+s. Pi wrote the HTML and displayed `Session exported to:`.
5. Verified session bytes were identical before these actions and after quitting. The baseline was taken after startup because Pi normally appends a `thinking_level_change` entry while opening the session.
6. Opened each generated HTML in real Chromium 153.0.8010.12 using Playwright, decoded the generated `session-data` contract, verified nonempty custom call and expanded result HTML for grep, find and `fm_watch_arm_pi`, expanded tool outputs, and verified their result text was visible. No viewer JavaScript errors occurred.
7. Captured screenshots of the real rendered exports.

The first browser launch lacked `libnspr4`, `libnss3`, `libgbm`, `libasound`, and `libdrm`. Resolved this by downloading Debian library archives and extracting them under the disposable worktree, using a process-local `LD_LIBRARY_PATH`. No system packages or global settings were changed. The optional Playwright MCP browser route also lacked Chrome; the final browser evidence uses the workspace-local browser instead.

Live driver commands:

`` `sh
python3 .test-calm-validation/live.py 101
python3 .test-calm-validation/live.py 100
LD_LIBRARY_PATH="$PWD/.test-calm-validation/browser-libs/root/usr/lib/x86_64-linux-gnu" \
  PLAYWRIGHT_BROWSERS_PATH="$PWD/.test-calm-validation/browser-binaries" \
  TMPDIR="$PWD/.test-calm-validation/tmp" \
  node .test-calm-validation/render.mjs
`` `

The live driver and browser check are preserved alongside this report as `live-driver.py` and `render-check.mjs`. Their disposable relative dependency paths existed only during this validation. CLI arguments, pty size, session hashes, and individual steps are recorded in `pi-101-live-result.json` and `pi-100-live-result.json`; custom rendered call/result text is recorded in `pi-101-rendered-output.json` and `pi-100-rendered-output.json`.

No full repository suite, linter, formatter, static analysis, pipeline-control, push, PR, or CI phase was run. All disposable installations, fixture homes, copied extensions, and temporary selector scripts were removed. No source changes were required.
Evidence: Pre-fix Pi 1.0.1 reproduction: grep disappeared from export HTML

Source: Pre-fix Pi 1.0.1 reproduction: grep disappeared from export HTML

not ok - Pi calm renderer and lifecycle contract failed: file://~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/tmp/fm-calm-pi-extension.MMry28/renderer/[eval1]:546
      throw new Error(`${name} disappeared from ${command} HTML while calm mode was on`);
            ^

Error: grep disappeared from /export calm.html HTML while calm mode was on
    at assertStockHtmlRendering (file://~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/tmp/fm-calm-pi-extension.MMry28/renderer/[eval1]:546:13)
    at file://~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/tmp/fm-calm-pi-extension.MMry28/renderer/[eval1]:553:7
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)

Node.js v24.20.0
![Pi 1.0.1 rendered Calm export](https://github.com/user-attachments/assets/108bf8b3-44f2-49fc-af60-27af2b109357) - Evidence: [Pi 1.0.1 rendered Calm export](https://github.com/cloud-practitioner/firstmate/blob/5dfe966d02a893f752171ef1699b94b37ee9822d/.no-mistakes/evidence/fm/fm-calm-pi-1-0-1-port/pi-101-calm-export.png) ![Pi 1.0.0 rendered Calm export](https://github.com/user-attachments/assets/5efc212c-05d5-46d0-9505-e73cdf582dbb) - Evidence: [Pi 1.0.0 rendered Calm export](https://github.com/cloud-practitioner/firstmate/blob/5dfe966d02a893f752171ef1699b94b37ee9822d/.no-mistakes/evidence/fm/fm-calm-pi-1-0-1-port/pi-100-calm-export.png)
Evidence: HTML generated by the real Pi 1.0.1 CLI

Source: HTML generated by the real Pi 1.0.1 CLI

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Session Export</title>
  <style>
    :root {
      --accent: #800080;
      --borderAccent: #800080;
      --customMessageLabel: #800080;
      --mdCode: #800080;
      --mdListBullet: #800080;
      --syntaxType: #800080;
      --border: #000080;
      --mdLink: #000080;
      --syntaxKeyword: #000080;
      --syntaxVariable: #008080;
      --success: #008000;
      --mdCodeBlock: #008000;
      --toolDiffAdded: #008000;
      --bashMode: #008000;
      --syntaxNumber: #800080;
      --error: #800000;
      --toolDiffRemoved: #800000;
      --warning: #808000;
      --mdHeading: #808000;
      --syntaxFunction: #808000;
      --syntaxString: #008000;
      --thinkingMinimal: #000080;
      --thinkingLow: #000080;
      --thinkingMedium: #008080;
      --thinkingHigh: #800080;
      --thinkingXhigh: #ff00ff;
      --thinkingMax: #800000;
      --text: #e5e5e7;
      --userMessageText: #e5e5e7;
      --customMessageText: #727273;
      --toolTitle: #e5e5e7;
      --syntaxOperator: #727273;
      --syntaxPunctuation: #727273;
      --muted: #727273;
      --dim: #727273;
      --thinkingText: #727273;
      --toolOutput: #727273;
      --mdLinkUrl: #727273;
      --mdQuote: #727273;
      --mdQuoteBorder: #727273;
      --mdHr: #727273;
      --mdCodeBlockBorder: #727273;
      --toolDiffContext: #727273;
      --syntaxComment: #727273;
      --scrollbarTrack: #727273;
      --scrollbarThumb: #727273;
      --searchMatchText: #727273;
      --borderMuted: #727273;
      --thinkingOff: #727273;
      --selectedBg: #000000;
      --searchMatchBg: #000000;
      --userMessageBg: #000000;
      --customMessageBg: #000000;
      --toolPendingBg: #000000;
      --toolSuccessBg: #000000;
      --toolErrorBg: #000000;
      --exportPageBg: rgb(0, 0, 0);
      --exportCardBg: rgb(0, 0, 0);
      --exportInfoBg: rgb(20, 15, 0);
      --body-bg: rgb(0, 0, 0);
      --container-bg: rgb(0, 0, 0);
      --info-bg: rgb(20, 15, 0);
    }

    * { margin: 0; padding: 0; box-sizing: border-box; }

    :root {
      --line-height: 18px; /* 12px font * 1.5 */
      --sidebar-width: 400px;
      --sidebar-min-width: 240px;
      --sidebar-max-width: 840px;
      --sidebar-resizer-width: 6px;
    }

    body {
      font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
      font-size: 12px;
      line-height: var(--line-height);
      color: var(--text);
      background: var(--body-bg);
    }

    body.sidebar-resizing {
      cursor: col-resize;
      user-select: none;
    }

    #app {
      display: flex;
      min-height: 100vh;
    }

    /* Sidebar */
    #sidebar {
      width: var(--sidebar-width);
      min-width: var(--sidebar-width);
      max-width: var(--sidebar-width);
      background: var(--container-bg);
      flex-shrink: 0;
      display: flex;
      flex-direction: column;
      position: sticky;
      top: 0;
      height: 100vh;
      border-right: 1px solid var(--dim);
    }

    #sidebar-resizer {
      width: var(--sidebar-resizer-width);
      flex-shrink: 0;
      position: sticky;
      top: 0;
      height: 100vh;
      cursor: col-resize;
      touch-action: none;
      background: transparent;
      border-right: 1px solid transparent;
    }

    #sidebar-resizer:hover,
    body.sidebar-resizing #sidebar-resizer {
      background: var(--selectedBg);
      border-right-color: var(--dim);
    }

    .sidebar-header {
      padding: 8px 12px;
      flex-shrink: 0;
    }

    .sidebar-controls {
      padding: 8px 8px 4px 8px;
    }

    .sidebar-search {
      width: 100%;
      box-sizing: border-box;
      padding: 4px 8px;
      font-size: 11px;
      font-family: inherit;
      background: var(--body-bg);
      color: var(--text);
      border: 1px solid var(--dim);
      border-radius: 3px;
    }

    .sidebar-filters {
      display: flex;
      padding: 4px 8px 8px 8px;
      gap: 4px;
      align-items: center;
      flex-wrap: wrap;
    }

    .sidebar-search:focus {
      outline: none;
      border-color: var(--accent);
    }

    .sidebar-search::placeholder {
      color: var(--muted);
    }

    .filter-btn {
      padding: 3px 8px;
      font-size: 10px;
      font-family: inherit;
      background: transparent;
      color: var(--muted);
      border: 1px solid var(--dim);
      border-radius: 3px;
      cursor: pointer;
    }

    .filter-btn:hover {
      color: var(--text);
      border-color: var(--text);
    }

    .filter-btn.active {
      background: var(--accent);
      color: var(--body-bg);
      border-color: var(--accent);
    }

    .sidebar-close {
      display: none;
      padding: 3px 8px;
      font-size: 12px;
      font-family: inherit;
      background: transparent;
      color: var(--muted);
      border: 1px solid var(--dim);
      border-radius: 3px;
      cursor: pointer;
      margin-left: auto;
    }

    .sidebar-close:hover {
      color: var(--text);
      border-color: var(--text);
    }

    .tree-container {
      flex: 1;
      overflow: auto;
      padding: 4px 0;
    }

    .tree-node {
      padding: 0 8px;
      cursor: pointer;
      display: flex;
      align-items: baseline;
      font-size: 11px;
      line-height: 13px;
      white-space: nowrap;
    }

    .tree-node:hover {
      background: var(--selectedBg);
    }

    .tree-node.active {
      background: var(--selectedBg);
    }

    .tree-node.active .tree-content {
      font-weight: bold;
    }

    .tree-node.in-path {
      background: color-mix(in srgb, var(--accent) 10%, transparent);
    }

    .tree-node:not(.in-path) {
      opacity: 0.5;
    }

    .tree-node:not(.in-path):hover {
      opacity: 1;
    }

    .tree-prefix {
      color: var(--muted);
      flex-shrink: 0;
      font-family: monospace;
      white-space: pre;
    }

    .tree-marker {
      color: var(--accent);
      flex-shrink: 0;
    }

    .tree-content {
      color: var(--text);
    }

    .tree-role-user {
      color: var(--accent);
    }

    .tree-role-skill {
      color: var(--customMessageLabel);
    }

    .tree-role-assistant {
      color: var(--success);
    }

    .tree-role-tool {
      color: var(--muted);
    }

    .tree-muted {
      color: var(--muted);
    }

    .tree-error {
      color: var(--error);
    }

    .tree-compaction {
      color: var(--borderAccent);
    }

    .tree-branch-summary {
      color: var(--warning);
    }

    .tree-custom-message {
      color: var(--customMessageLabel);
    }

    .tree-status {
      padding: 4px 12px;
      font-size: 10px;
      color: var(--muted);
      flex-shrink: 0;
    }

    /* Main content */
    #content {
      flex: 1;
      min-width: 0;
      overflow-y: auto;
      padding: var(--line-height) calc(var(--line-height) * 2);
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    #content > * {
      width: 100%;
      max-width: 800px;
    }

    /* Help bar */
    .help-bar {
      font-size: 11px;
      color: var(--warning);
      margin-bottom: var(--line-height);
      display: flex;
      align-items: center;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 12px;
    }

    .help-hint {
      flex: 1 1 240px;
    }

    .help-actions {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: 8px;
    }

    .header-toggle-btn,
    .download-json-btn {
      font-size: 10px;
      padding: 2px 8px;
      background: var(--container-bg);
      border: 1px solid var(--border);
      border-radius: 3px;
      color: var(--text);
      cursor: pointer;
      font-family: inherit;
    }

    .header-toggle-btn:hover,
    .download-json-btn:hover {
      background: var(--hover);
      border-color: var(--borderAccent);
    }

    .header-toggle-btn[aria-pressed="true"] {
      color: var(--accent);
      border-color: var(--accent);
      font-weight: bold;
    }

    /* Header */
    .header {
      background: var(--container-bg);
      border-radius: 4px;
      paddin

... [268926 bytes truncated] ...

hare:v1:sidebar-width';
      const MIN_CONTENT_WIDTH = 320;

      function isMobileLayout() {
        return window.matchMedia('(max-width: 900px)').matches;
      }

      function getSidebarBounds() {
        const rootStyles = getComputedStyle(document.documentElement);
        const minWidth = parseFloat(rootStyles.getPropertyValue('--sidebar-min-width')) || 240;
        const maxWidth = parseFloat(rootStyles.getPropertyValue('--sidebar-max-width')) || 720;
        const viewportMaxWidth = window.innerWidth - MIN_CONTENT_WIDTH;
        return {
          minWidth,
          maxWidth: Math.max(minWidth, Math.min(maxWidth, viewportMaxWidth))
        };
      }

      function clampSidebarWidth(width) {
        const { minWidth, maxWidth } = getSidebarBounds();
        return Math.max(minWidth, Math.min(maxWidth, width));
      }

      function applySidebarWidth(width) {
        document.documentElement.style.setProperty('--sidebar-width', `${Math.round(clampSidebarWidth(width))}px`);
      }

      function loadSidebarWidth() {
        try {
          const raw = localStorage.getItem(SIDEBAR_WIDTH_STORAGE_KEY);
          if (raw === null) return null;
          const width = Number(raw);
          return Number.isFinite(width) ? width : null;
        } catch {
          return null;
        }
      }

      function saveSidebarWidth(width) {
        try {
          localStorage.setItem(SIDEBAR_WIDTH_STORAGE_KEY, String(Math.round(clampSidebarWidth(width))));
        } catch {
          // Ignore storage failures (e.g. private browsing restrictions)
        }
      }

      function setupSidebarResize() {
        const savedWidth = loadSidebarWidth();
        if (savedWidth !== null) {
          applySidebarWidth(savedWidth);
        }

        if (!sidebarResizer) return;

        let cleanupDrag = null;

        const stopDrag = (pointerId) => {
          if (cleanupDrag) {
            cleanupDrag(pointerId);
            cleanupDrag = null;
          }
        };

        sidebarResizer.addEventListener('pointerdown', (e) => {
          if (isMobileLayout()) return;

          e.preventDefault();
          const startX = e.clientX;
          const startWidth = sidebar.getBoundingClientRect().width;
          document.body.classList.add('sidebar-resizing');
          sidebarResizer.setPointerCapture?.(e.pointerId);

          const onPointerMove = (event) => {
            applySidebarWidth(startWidth + (event.clientX - startX));
          };

          cleanupDrag = (pointerIdToRelease) => {
            document.body.classList.remove('sidebar-resizing');
            sidebarResizer.releasePointerCapture?.(pointerIdToRelease);
            window.removeEventListener('pointermove', onPointerMove);
            window.removeEventListener('pointerup', onPointerUp);
            window.removeEventListener('pointercancel', onPointerCancel);
            saveSidebarWidth(sidebar.getBoundingClientRect().width);
          };

          const onPointerUp = (event) => stopDrag(event.pointerId);
          const onPointerCancel = (event) => stopDrag(event.pointerId);

          window.addEventListener('pointermove', onPointerMove);
          window.addEventListener('pointerup', onPointerUp);
          window.addEventListener('pointercancel', onPointerCancel);
        });

        sidebarResizer.addEventListener('dblclick', () => {
          if (isMobileLayout()) return;
          applySidebarWidth(400);
          saveSidebarWidth(400);
        });

        window.addEventListener('resize', () => {
          if (isMobileLayout()) return;
          applySidebarWidth(sidebar.getBoundingClientRect().width);
        });
      }

      setupSidebarResize();

      hamburger.addEventListener('click', () => {
        sidebar.classList.add('open');
        overlay.classList.add('open');
        hamburger.style.display = 'none';
      });

      const closeSidebar = () => {
        sidebar.classList.remove('open');
        overlay.classList.remove('open');
        hamburger.style.display = '';
      };

      overlay.addEventListener('click', closeSidebar);
      document.getElementById('sidebar-close').addEventListener('click', closeSidebar);

      // Toggle states
      let thinkingExpanded = true;
      let toolOutputsExpanded = false;
      let showHiddenMessages = false;

      function setHiddenMessagesVisible(visible) {
        showHiddenMessages = visible;
        document.body.classList.toggle('show-hidden-messages', visible);
        const button = document.querySelector('[data-action="toggle-hidden-messages"]');
        if (button) {
          button.setAttribute('aria-pressed', String(visible));
          button.textContent = visible ? 'Hide hidden messages' : 'Show hidden messages';
        }
      }

      function setThinkingExpanded(expanded) {
        thinkingExpanded = expanded;
        document.querySelector('[data-action="toggle-thinking"]')?.setAttribute('aria-pressed', String(expanded));
        document.querySelectorAll('.thinking-text').forEach(el => {
          el.style.display = thinkingExpanded ? '' : 'none';
        });
        document.querySelectorAll('.thinking-collapsed').forEach(el => {
          el.style.display = thinkingExpanded ? 'none' : 'block';
        });
      }

      function setToolOutputsExpanded(expanded) {
        toolOutputsExpanded = expanded;
        document.querySelector('[data-action="toggle-tools"]')?.setAttribute('aria-pressed', String(expanded));
        document.querySelectorAll('.tool-output.expandable').forEach(el => {
          el.classList.toggle('expanded', toolOutputsExpanded);
        });
        document.querySelectorAll('.compaction').forEach(el => {
          el.classList.toggle('expanded', toolOutputsExpanded);
        });
        document.querySelectorAll('.skill-invocation').forEach(el => {
          el.classList.toggle('expanded', toolOutputsExpanded);
        });
      }

      const attachHeaderHandlers = () => {
        document.querySelector('[data-action="toggle-thinking"]')?.addEventListener('click', () => {
          setThinkingExpanded(!thinkingExpanded);
        });
        document.querySelector('[data-action="toggle-tools"]')?.addEventListener('click', () => {
          setToolOutputsExpanded(!toolOutputsExpanded);
        });
        document.querySelector('[data-action="toggle-hidden-messages"]')?.addEventListener('click', () => {
          setHiddenMessagesVisible(!showHiddenMessages);
        });
      };

      const isEditableTarget = (element) => {
        if (!element) return false;
        const tagName = element.tagName;
        if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT' || tagName === 'BUTTON') {
          return true;
        }
        return element.isContentEditable || Boolean(element.closest?.('[contenteditable="true"]'));
      };

      // Keyboard shortcuts
      document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
          searchInput.value = '';
          searchQuery = '';
          navigateTo(leafId, 'bottom');
        }

        if (e.ctrlKey || e.metaKey || e.altKey || isEditableTarget(document.activeElement)) {
          return;
        }

        const key = e.key.toLowerCase();
        if (key === 't') {
          e.preventDefault();
          setThinkingExpanded(!thinkingExpanded);
        } else if (key === 'o') {
          e.preventDefault();
          setToolOutputsExpanded(!toolOutputsExpanded);
        } else if (key === 'h') {
          e.preventDefault();
          setHiddenMessagesVisible(!showHiddenMessages);
        }
      });

      // Initial render
      // If URL has targetId, scroll to that specific message; otherwise stay at top
      if (leafId) {
        if (urlTargetId && byId.has(urlTargetId)) {
          // Deep link: navigate to leaf and scroll to target message
          navigateTo(leafId, 'target', urlTargetId);
        } else {
          navigateTo(leafId, 'none');
        }
      } else if (entries.length > 0) {
        // Fallback: use last entry if no leafId
        navigateTo(entries[entries.length - 1].id, 'none');
      }
    })();

  </script>
</body>
</html>
Evidence: Pi 1.0.1 live CLI actions, terminal dimensions, and session hashes

Source: Pi 1.0.1 live CLI actions, terminal dimensions, and session hashes

{
  "version": "101",
  "command": [
    "node",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/pi-101-global/lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js",
    "--offline",
    "--tui-mode",
    "regular",
    "--no-context-files",
    "--no-skills",
    "--no-prompt-templates",
    "--no-themes",
    "--no-extensions",
    "--approve",
    "-e",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-101/project/.pi/extensions/fm-calm.ts",
    "-e",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-101/project/.pi/extensions/fm-primary-pi-watch.ts",
    "--session",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-101/session.jsonl"
  ],
  "pty": {
    "rows": 44,
    "columns": 160
  },
  "steps": [
    "Real /calm command toggled off then on and persisted each choice.",
    "Enter with submit remapped to Alt+s did not create the HTML export.",
    "Alt+s executed /export and real Pi confirmed its output path.",
    "Original session bytes remained unchanged through toggles and export."
  ],
  "export": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-101-calm-export.html",
  "sessionSha256Before": "3703e8d31915be3899d883785dd123a98b9a3de6d714e3b71dd18954b207c4ad",
  "sessionSha256After": "3703e8d31915be3899d883785dd123a98b9a3de6d714e3b71dd18954b207c4ad",
  "exit": 0
}
Evidence: Pi 1.0.0 live CLI actions, terminal dimensions, and session hashes

Source: Pi 1.0.0 live CLI actions, terminal dimensions, and session hashes

{
  "version": "100",
  "command": [
    "node",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/pi-100-global/lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js",
    "--offline",
    "--tui-mode",
    "regular",
    "--no-context-files",
    "--no-skills",
    "--no-prompt-templates",
    "--no-themes",
    "--no-extensions",
    "--approve",
    "-e",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-100/project/.pi/extensions/fm-calm.ts",
    "-e",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-100/project/.pi/extensions/fm-primary-pi-watch.ts",
    "--session",
    "~/.no-mistakes/worktrees/450411b3e67c/01M4255Z93913VQ2VRJNN7CD28/.test-calm-validation/live-100/session.jsonl"
  ],
  "pty": {
    "rows": 44,
    "columns": 160
  },
  "steps": [
    "Real /calm command toggled off then on and persisted each choice.",
    "Enter with submit remapped to Alt+s did not create the HTML export.",
    "Alt+s executed /export and real Pi confirmed its output path.",
    "Original session bytes remained unchanged through toggles and export."
  ],
  "export": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-100-calm-export.html",
  "sessionSha256Before": "1a4deb2505be3b0644416df61a9bd2b59428458f5acfd24a57371e5a4e3ac062",
  "sessionSha256After": "1a4deb2505be3b0644416df61a9bd2b59428458f5acfd24a57371e5a4e3ac062",
  "exit": 0
}
Evidence: Browser-observed custom call and result output for both versions

Source: Browser-observed custom call and result output for both versions

[
  {
    "version": "101",
    "browser": "153.0.8010.12",
    "file": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-101-calm-export.html",
    "screenshot": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-101-calm-export.png",
    "renderedTools": {
      "call_grep": {
        "call": "grep /CALM_LIVE_GREP/ in .",
        "result": "sample.txt:1:CALM_LIVE_GREP"
      },
      "call_find": {
        "call": "find CALM_LIVE_FIND* in .",
        "result": "CALM_LIVE_FIND.txt"
      },
      "call_fm_watch_arm_pi": {
        "call": "fm_watch_arm_pi",
        "result": "watcher: saved fixture arm result"
      }
    },
    "errors": []
  },
  {
    "version": "100",
    "browser": "153.0.8010.12",
    "file": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-100-calm-export.html",
    "screenshot": "~/.no-mistakes/evidence/01M4255Z93913VQ2VRJNN7CD28/pi-100-calm-export.png",
    "renderedTools": {
      "call_grep": {
        "call": "grep /CALM_LIVE_GREP/ in .",
        "result": "sample.txt:1:CALM_LIVE_GREP"
      },
      "call_find": {
        "call": "find CALM_LIVE_FIND* in .",
        "result": "CALM_LIVE_FIND.txt"
      },
      "call_fm_watch_arm_pi": {
        "call": "fm_watch_arm_pi",
        "result": "watcher: saved fixture arm result"
      }
    },
    "errors": []
  }
]

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
Export a Calm session on Pi 1.0.1 without losing grep, find, or watcher HTML ✅ pass live pi-101-calm-export.html, pi-101-calm-export.png, pi-101-rendered-output.json; targeted baseline and target contract logs
Export the same Calm session on Pi 1.0.0 while retaining older renderer compatibility ✅ pass live pi-100-calm-export.html, pi-100-calm-export.png, pi-100-rendered-output.json; contract-current-pi-100.log
Remap submit to Alt+s and prevent ordinary Enter from activating an export ✅ pass live pi-101-live-result.json, pi-100-live-result.json, and corresponding terminal transcripts
  • Inspected git diff 93d82355e4c37dbe4b91aba8d4e16f2789e7938e 104b70f1309fed605e42120963503528bd9d70be.
  • Installed Pi 1.0.1 and 1.0.0 into disposable, worktree-local npm prefixes without changing the machine installation.
  • Executed only test_rendering_and_session_lifecycle from the baseline and target revisions using temporary selectors tests/.calm-validation-{baseline,current}.sh, with isolated HOME, TMPDIR, and explicit FM_PI_PACKAGE_DIR.
  • Ran python3 .test-calm-validation/live.py 101 and python3 .test-calm-validation/live.py 100 against real Pi CLIs in sized 160×44 pseudo-terminals.
  • Drove /calm off/on, attempted /export with unmatched Enter, submitted with Alt+s, and verified export confirmation, persisted Calm state, and unchanged session bytes after startup.
  • Ran node .test-calm-validation/render.mjs using workspace-local Chromium and libraries; verified custom call/result HTML and visible grep, find, and watcher outputs, then captured screenshots.
  • Removed disposable installations, homes, copied extensions, browser dependencies, and selector scripts; confirmed a clean worktree and no remaining fixture processes.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Supply getToolRenderers alongside getToolDefinition in both Calm HTML-renderer fixtures, and record the Pi 1.0.1 verification evidence.

Ported from upstream kunchenguid#6512 by @mremond.
Both upstream commits are combined into one; applied unchanged.
@cloud-practitioner
cloud-practitioner merged commit 2b338bd into main Oct 5, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant