Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

Description

@pseudoseed

Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
main, but Tower does not serve it and the root build does not include it, so today it runs only from
a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
open, not something you use.

The plan section is reproduced verbatim so it is not fetched by path.


Phase 12: codev-client tiling and mobile

Dependencies: Phase 7, Phase 11

Objective

The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
measurements the spec gives, because these criteria are arithmetic and a component test cannot
check them.

Files to Create / Modify

  • apps/client/src/grid/ — the tile grid.
  • apps/client/src/pane/ — a builder pane.
  • apps/client/src/architect-strip/
  • apps/client/src/responsive/ — paging behaviour.
  • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

Deliverables

  • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
    text at 13px or larger, each pane showing builder id, status, and the last three agent
    messages.
  • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
    below the grid showing status and its last message, and expands to a full pane on demand,
    replacing the grid. A seventh equal tile is offered only at 1920 or wider.
  • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
    rather than shrinks.
  • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
  • Tests for this phase.

Acceptance Criteria

  • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
    asserted from the rendered page, not from the stylesheet.
  • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
    a seventh tile appears at 1920.
  • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
    viewport width.
  • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
    recorded with what was actually done.
  • Build and tests pass.

Test Plan

E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

Manual: the iPad run, over the tailnet, with the teardown step
(tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
written.



Required scope beyond the plan — #228, inlined by the architect

Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
All four are required here. Read #228 in full; this is the summary, not the substitute.

  1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
    already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
    apps/client to the root build script. Today scripts/serve.mjs is the only server for the
    built bundle.

  2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
    with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
    Wiring one makes that test fail; that is intended. The reason it was deferred is real:
    t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
    subscription plus per-workspace t3 config Tower does not currently hold. Without it every
    non-blocked row renders UNKNOWN.

  3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
    refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
    operator must leave the client for the CLI. Do not fix this with a request timeout. A client
    that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
    anyway — reporting one outcome while another happened. Submit, poll, report.

  4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
    tests. There is no afx command a human can run, so the approval path is test-reachable only —
    9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
    terminal and never to a log or argv. Revocation currently requires a credential the operator
    does not hold
    (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
    so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
    privileged; the asymmetry runs the wrong way.

Architect notes

Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
confirmation that flashed and vanished. Open it and look before calling anything done.

Suites, because a green run is evidence only about what it ran. Five exist:
packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

Phase 11's known limits, carried: wire types are hand-mirrored in
apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
protocol.statuses at 98% of payload that no client reads (#225).

Do not enable thread-backing on any workspace that receives porch --no-enter gate
notifications.
A thread-backed architect receives no gate notice — correct for a composer-less
transport, and an open decision.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

      Description

      @pseudoseed

      Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
      main, but Tower does not serve it and the root build does not include it, so today it runs only from
      a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
      open, not something you use.

      The plan section is reproduced verbatim so it is not fetched by path.


      Phase 12: codev-client tiling and mobile

      Dependencies: Phase 7, Phase 11

      Objective

      The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
      measurements the spec gives, because these criteria are arithmetic and a component test cannot
      check them.

      Files to Create / Modify

      • apps/client/src/grid/ — the tile grid.
      • apps/client/src/pane/ — a builder pane.
      • apps/client/src/architect-strip/
      • apps/client/src/responsive/ — paging behaviour.
      • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

      Deliverables

      • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
        text at 13px or larger, each pane showing builder id, status, and the last three agent
        messages.
      • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
        below the grid showing status and its last message, and expands to a full pane on demand,
        replacing the grid. A seventh equal tile is offered only at 1920 or wider.
      • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
        rather than shrinks.
      • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
      • Tests for this phase.

      Acceptance Criteria

      • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
        asserted from the rendered page, not from the stylesheet.
      • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
        a seventh tile appears at 1920.
      • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
        viewport width.
      • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
        recorded with what was actually done.
      • Build and tests pass.

      Test Plan

      E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

      Manual: the iPad run, over the tailnet, with the teardown step
      (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
      written.



      Required scope beyond the plan — #228, inlined by the architect

      Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
      All four are required here. Read #228 in full; this is the summary, not the substitute.

      1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
        already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
        apps/client to the root build script. Today scripts/serve.mjs is the only server for the
        built bundle.

      2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
        with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
        Wiring one makes that test fail; that is intended. The reason it was deferred is real:
        t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
        subscription plus per-workspace t3 config Tower does not currently hold. Without it every
        non-blocked row renders UNKNOWN.

      3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
        refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
        operator must leave the client for the CLI. Do not fix this with a request timeout. A client
        that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
        anyway — reporting one outcome while another happened. Submit, poll, report.

      4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
        tests. There is no afx command a human can run, so the approval path is test-reachable only —
        9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
        terminal and never to a log or argv. Revocation currently requires a credential the operator
        does not hold
        (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
        so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
        privileged; the asymmetry runs the wrong way.

      Architect notes

      Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

      A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
      tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
      unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
      invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
      blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
      confirmation that flashed and vanished. Open it and look before calling anything done.

      Suites, because a green run is evidence only about what it ran. Five exist:
      packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
      apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
      configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

      Phase 11's known limits, carried: wire types are hand-mirrored in
      apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
      tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
      protocol.statuses at 98% of payload that no client reads (#225).

      Do not enable thread-backing on any workspace that receives porch --no-enter gate
      notifications.
      A thread-backed architect receives no gate notice — correct for a composer-less
      transport, and an open decision.

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

          Description

          @pseudoseed

          Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
          main, but Tower does not serve it and the root build does not include it, so today it runs only from
          a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
          open, not something you use.

          The plan section is reproduced verbatim so it is not fetched by path.


          Phase 12: codev-client tiling and mobile

          Dependencies: Phase 7, Phase 11

          Objective

          The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
          measurements the spec gives, because these criteria are arithmetic and a component test cannot
          check them.

          Files to Create / Modify

          • apps/client/src/grid/ — the tile grid.
          • apps/client/src/pane/ — a builder pane.
          • apps/client/src/architect-strip/
          • apps/client/src/responsive/ — paging behaviour.
          • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

          Deliverables

          • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
            text at 13px or larger, each pane showing builder id, status, and the last three agent
            messages.
          • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
            below the grid showing status and its last message, and expands to a full pane on demand,
            replacing the grid. A seventh equal tile is offered only at 1920 or wider.
          • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
            rather than shrinks.
          • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
          • Tests for this phase.

          Acceptance Criteria

          • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
            asserted from the rendered page, not from the stylesheet.
          • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
            a seventh tile appears at 1920.
          • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
            viewport width.
          • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
            recorded with what was actually done.
          • Build and tests pass.

          Test Plan

          E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

          Manual: the iPad run, over the tailnet, with the teardown step
          (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
          written.



          Required scope beyond the plan — #228, inlined by the architect

          Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
          All four are required here. Read #228 in full; this is the summary, not the substitute.

          1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
            already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
            apps/client to the root build script. Today scripts/serve.mjs is the only server for the
            built bundle.

          2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
            with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
            Wiring one makes that test fail; that is intended. The reason it was deferred is real:
            t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
            subscription plus per-workspace t3 config Tower does not currently hold. Without it every
            non-blocked row renders UNKNOWN.

          3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
            refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
            operator must leave the client for the CLI. Do not fix this with a request timeout. A client
            that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
            anyway — reporting one outcome while another happened. Submit, poll, report.

          4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
            tests. There is no afx command a human can run, so the approval path is test-reachable only —
            9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
            terminal and never to a log or argv. Revocation currently requires a credential the operator
            does not hold
            (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
            so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
            privileged; the asymmetry runs the wrong way.

          Architect notes

          Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

          A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
          tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
          unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
          invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
          blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
          confirmation that flashed and vanished. Open it and look before calling anything done.

          Suites, because a green run is evidence only about what it ran. Five exist:
          packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
          apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
          configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

          Phase 11's known limits, carried: wire types are hand-mirrored in
          apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
          tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
          protocol.statuses at 98% of payload that no client reads (#225).

          Do not enable thread-backing on any workspace that receives porch --no-enter gate
          notifications.
          A thread-backed architect receives no gate notice — correct for a composer-less
          transport, and an open decision.

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

              Description

              @pseudoseed

              Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
              main, but Tower does not serve it and the root build does not include it, so today it runs only from
              a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
              open, not something you use.

              The plan section is reproduced verbatim so it is not fetched by path.


              Phase 12: codev-client tiling and mobile

              Dependencies: Phase 7, Phase 11

              Objective

              The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
              measurements the spec gives, because these criteria are arithmetic and a component test cannot
              check them.

              Files to Create / Modify

              • apps/client/src/grid/ — the tile grid.
              • apps/client/src/pane/ — a builder pane.
              • apps/client/src/architect-strip/
              • apps/client/src/responsive/ — paging behaviour.
              • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

              Deliverables

              • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
                text at 13px or larger, each pane showing builder id, status, and the last three agent
                messages.
              • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
                below the grid showing status and its last message, and expands to a full pane on demand,
                replacing the grid. A seventh equal tile is offered only at 1920 or wider.
              • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
                rather than shrinks.
              • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
              • Tests for this phase.

              Acceptance Criteria

              • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
                asserted from the rendered page, not from the stylesheet.
              • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
                a seventh tile appears at 1920.
              • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
                viewport width.
              • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
                recorded with what was actually done.
              • Build and tests pass.

              Test Plan

              E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

              Manual: the iPad run, over the tailnet, with the teardown step
              (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
              written.



              Required scope beyond the plan — #228, inlined by the architect

              Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
              All four are required here. Read #228 in full; this is the summary, not the substitute.

              1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
                already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
                apps/client to the root build script. Today scripts/serve.mjs is the only server for the
                built bundle.

              2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
                with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
                Wiring one makes that test fail; that is intended. The reason it was deferred is real:
                t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
                subscription plus per-workspace t3 config Tower does not currently hold. Without it every
                non-blocked row renders UNKNOWN.

              3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
                refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
                operator must leave the client for the CLI. Do not fix this with a request timeout. A client
                that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
                anyway — reporting one outcome while another happened. Submit, poll, report.

              4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
                tests. There is no afx command a human can run, so the approval path is test-reachable only —
                9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
                terminal and never to a log or argv. Revocation currently requires a credential the operator
                does not hold
                (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
                so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
                privileged; the asymmetry runs the wrong way.

              Architect notes

              Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

              A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
              tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
              unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
              invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
              blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
              confirmation that flashed and vanished. Open it and look before calling anything done.

              Suites, because a green run is evidence only about what it ran. Five exist:
              packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
              apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
              configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

              Phase 11's known limits, carried: wire types are hand-mirrored in
              apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
              tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
              protocol.statuses at 98% of payload that no client reads (#225).

              Do not enable thread-backing on any workspace that receives porch --no-enter gate
              notifications.
              A thread-backed architect receives no gate notice — correct for a composer-less
              transport, and an open decision.

              Activity

              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

                  Description

                  @pseudoseed

                  Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
                  main, but Tower does not serve it and the root build does not include it, so today it runs only from
                  a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
                  open, not something you use.

                  The plan section is reproduced verbatim so it is not fetched by path.


                  Phase 12: codev-client tiling and mobile

                  Dependencies: Phase 7, Phase 11

                  Objective

                  The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
                  measurements the spec gives, because these criteria are arithmetic and a component test cannot
                  check them.

                  Files to Create / Modify

                  • apps/client/src/grid/ — the tile grid.
                  • apps/client/src/pane/ — a builder pane.
                  • apps/client/src/architect-strip/
                  • apps/client/src/responsive/ — paging behaviour.
                  • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

                  Deliverables

                  • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
                    text at 13px or larger, each pane showing builder id, status, and the last three agent
                    messages.
                  • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
                    below the grid showing status and its last message, and expands to a full pane on demand,
                    replacing the grid. A seventh equal tile is offered only at 1920 or wider.
                  • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
                    rather than shrinks.
                  • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
                  • Tests for this phase.

                  Acceptance Criteria

                  • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
                    asserted from the rendered page, not from the stylesheet.
                  • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
                    a seventh tile appears at 1920.
                  • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
                    viewport width.
                  • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
                    recorded with what was actually done.
                  • Build and tests pass.

                  Test Plan

                  E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

                  Manual: the iPad run, over the tailnet, with the teardown step
                  (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
                  written.



                  Required scope beyond the plan — #228, inlined by the architect

                  Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
                  All four are required here. Read #228 in full; this is the summary, not the substitute.

                  1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
                    already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
                    apps/client to the root build script. Today scripts/serve.mjs is the only server for the
                    built bundle.

                  2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
                    with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
                    Wiring one makes that test fail; that is intended. The reason it was deferred is real:
                    t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
                    subscription plus per-workspace t3 config Tower does not currently hold. Without it every
                    non-blocked row renders UNKNOWN.

                  3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
                    refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
                    operator must leave the client for the CLI. Do not fix this with a request timeout. A client
                    that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
                    anyway — reporting one outcome while another happened. Submit, poll, report.

                  4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
                    tests. There is no afx command a human can run, so the approval path is test-reachable only —
                    9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
                    terminal and never to a log or argv. Revocation currently requires a credential the operator
                    does not hold
                    (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
                    so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
                    privileged; the asymmetry runs the wrong way.

                  Architect notes

                  Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

                  A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
                  tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
                  unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
                  invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
                  blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
                  confirmation that flashed and vanished. Open it and look before calling anything done.

                  Suites, because a green run is evidence only about what it ran. Five exist:
                  packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
                  apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
                  configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

                  Phase 11's known limits, carried: wire types are hand-mirrored in
                  apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
                  tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
                  protocol.statuses at 98% of payload that no client reads (#225).

                  Do not enable thread-backing on any workspace that receives porch --no-enter gate
                  notifications.
                  A thread-backed architect receives no gate notice — correct for a composer-less
                  transport, and an open decision.

                  Activity

                  Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

                      Description

                      @pseudoseed

                      Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
                      main, but Tower does not serve it and the root build does not include it, so today it runs only from
                      a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
                      open, not something you use.

                      The plan section is reproduced verbatim so it is not fetched by path.


                      Phase 12: codev-client tiling and mobile

                      Dependencies: Phase 7, Phase 11

                      Objective

                      The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
                      measurements the spec gives, because these criteria are arithmetic and a component test cannot
                      check them.

                      Files to Create / Modify

                      • apps/client/src/grid/ — the tile grid.
                      • apps/client/src/pane/ — a builder pane.
                      • apps/client/src/architect-strip/
                      • apps/client/src/responsive/ — paging behaviour.
                      • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

                      Deliverables

                      • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
                        text at 13px or larger, each pane showing builder id, status, and the last three agent
                        messages.
                      • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
                        below the grid showing status and its last message, and expands to a full pane on demand,
                        replacing the grid. A seventh equal tile is offered only at 1920 or wider.
                      • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
                        rather than shrinks.
                      • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
                      • Tests for this phase.

                      Acceptance Criteria

                      • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
                        asserted from the rendered page, not from the stylesheet.
                      • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
                        a seventh tile appears at 1920.
                      • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
                        viewport width.
                      • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
                        recorded with what was actually done.
                      • Build and tests pass.

                      Test Plan

                      E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

                      Manual: the iPad run, over the tailnet, with the teardown step
                      (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
                      written.



                      Required scope beyond the plan — #228, inlined by the architect

                      Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
                      All four are required here. Read #228 in full; this is the summary, not the substitute.

                      1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
                        already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
                        apps/client to the root build script. Today scripts/serve.mjs is the only server for the
                        built bundle.

                      2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
                        with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
                        Wiring one makes that test fail; that is intended. The reason it was deferred is real:
                        t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
                        subscription plus per-workspace t3 config Tower does not currently hold. Without it every
                        non-blocked row renders UNKNOWN.

                      3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
                        refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
                        operator must leave the client for the CLI. Do not fix this with a request timeout. A client
                        that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
                        anyway — reporting one outcome while another happened. Submit, poll, report.

                      4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
                        tests. There is no afx command a human can run, so the approval path is test-reachable only —
                        9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
                        terminal and never to a log or argv. Revocation currently requires a credential the operator
                        does not hold
                        (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
                        so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
                        privileged; the asymmetry runs the wrong way.

                      Architect notes

                      Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

                      A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
                      tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
                      unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
                      invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
                      blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
                      confirmation that flashed and vanished. Open it and look before calling anything done.

                      Suites, because a green run is evidence only about what it ran. Five exist:
                      packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
                      apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
                      configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

                      Phase 11's known limits, carried: wire types are hand-mirrored in
                      apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
                      tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
                      protocol.statuses at 98% of payload that no client reads (#225).

                      Do not enable thread-backing on any workspace that receives porch --no-enter gate
                      notifications.
                      A thread-backed architect receives no gate notice — correct for a composer-less
                      transport, and an open decision.

                      Activity

                      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

                          Description

                          @pseudoseed

                          Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
                          main, but Tower does not serve it and the root build does not include it, so today it runs only from
                          a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
                          open, not something you use.

                          The plan section is reproduced verbatim so it is not fetched by path.


                          Phase 12: codev-client tiling and mobile

                          Dependencies: Phase 7, Phase 11

                          Objective

                          The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
                          measurements the spec gives, because these criteria are arithmetic and a component test cannot
                          check them.

                          Files to Create / Modify

                          • apps/client/src/grid/ — the tile grid.
                          • apps/client/src/pane/ — a builder pane.
                          • apps/client/src/architect-strip/
                          • apps/client/src/responsive/ — paging behaviour.
                          • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

                          Deliverables

                          • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
                            text at 13px or larger, each pane showing builder id, status, and the last three agent
                            messages.
                          • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
                            below the grid showing status and its last message, and expands to a full pane on demand,
                            replacing the grid. A seventh equal tile is offered only at 1920 or wider.
                          • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
                            rather than shrinks.
                          • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
                          • Tests for this phase.

                          Acceptance Criteria

                          • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
                            asserted from the rendered page, not from the stylesheet.
                          • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
                            a seventh tile appears at 1920.
                          • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
                            viewport width.
                          • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
                            recorded with what was actually done.
                          • Build and tests pass.

                          Test Plan

                          E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

                          Manual: the iPad run, over the tailnet, with the teardown step
                          (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
                          written.



                          Required scope beyond the plan — #228, inlined by the architect

                          Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
                          All four are required here. Read #228 in full; this is the summary, not the substitute.

                          1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
                            already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
                            apps/client to the root build script. Today scripts/serve.mjs is the only server for the
                            built bundle.

                          2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
                            with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
                            Wiring one makes that test fail; that is intended. The reason it was deferred is real:
                            t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
                            subscription plus per-workspace t3 config Tower does not currently hold. Without it every
                            non-blocked row renders UNKNOWN.

                          3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
                            refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
                            operator must leave the client for the CLI. Do not fix this with a request timeout. A client
                            that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
                            anyway — reporting one outcome while another happened. Submit, poll, report.

                          4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
                            tests. There is no afx command a human can run, so the approval path is test-reachable only —
                            9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
                            terminal and never to a log or argv. Revocation currently requires a credential the operator
                            does not hold
                            (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
                            so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
                            privileged; the asymmetry runs the wrong way.

                          Architect notes

                          Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

                          A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
                          tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
                          unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
                          invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
                          blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
                          confirmation that flashed and vanished. Open it and look before calling anything done.

                          Suites, because a green run is evidence only about what it ran. Five exist:
                          packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
                          apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
                          configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

                          Phase 11's known limits, carried: wire types are hand-mirrored in
                          apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
                          tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
                          protocol.statuses at 98% of payload that no client reads (#225).

                          Do not enable thread-backing on any workspace that receives porch --no-enter gate
                          notifications.
                          A thread-backed architect receives no gate notice — correct for a composer-less
                          transport, and an open decision.

                          Activity

                          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Spec 146 phase 12: codev-client tiling, mobile, and the static mount #234

                              Description

                              @pseudoseed

                              Spec 146 phase 12. This is the phase that makes the client usable. Phase 11 put apps/client on
                              main, but Tower does not serve it and the root build does not include it, so today it runs only from
                              a checkout via pnpm dev:servers + pnpm dev. Until this lands, the client is something you can
                              open, not something you use.

                              The plan section is reproduced verbatim so it is not fetched by path.


                              Phase 12: codev-client tiling and mobile

                              Dependencies: Phase 7, Phase 11

                              Objective

                              The tiled pane grid, the architect strip, and the narrow-viewport paging — verified at the exact
                              measurements the spec gives, because these criteria are arithmetic and a component test cannot
                              check them.

                              Files to Create / Modify

                              • apps/client/src/grid/ — the tile grid.
                              • apps/client/src/pane/ — a builder pane.
                              • apps/client/src/architect-strip/
                              • apps/client/src/responsive/ — paging behaviour.
                              • apps/client/e2e/tiling.spec.ts, apps/client/e2e/mobile.spec.ts

                              Deliverables

                              • Six builders tile at 1440x900 in a 3x2 grid, every pane at least 340x240 CSS px, body
                                text at 13px or larger, each pane showing builder id, status, and the last three agent
                                messages.
                              • The architect does not occupy a seventh tile at 1440x900. It gets a persistent strip
                                below the grid showing status and its last message, and expands to a full pane on demand,
                                replacing the grid. A seventh equal tile is offered only at 1920 or wider.
                              • At 390px wide the same six page one-per-screen with no horizontal scroll. The grid pages
                                rather than shrinks.
                              • Reachable over a tailnet from an iPad, and able to drive a builder to completion from there.
                              • Tests for this phase.

                              Acceptance Criteria

                              • Criterion 4, measured under Playwright at 1440x900: pane dimensions and computed font size
                                asserted from the rendered page, not from the stylesheet.
                              • Criterion 4b: no seventh tile at 1440x900; the strip is present; expansion replaces the grid;
                                a seventh tile appears at 1920.
                              • Criterion 5, measured at 390px: document.documentElement.scrollWidth does not exceed the
                                viewport width.
                              • Criterion 6: an iPad on the tailnet drives a builder to completion. This one is manual and is
                                recorded with what was actually done.
                              • Build and tests pass.

                              Test Plan

                              E2E under Playwright at 1440x900, 1920x1080 and 390px, asserting measured geometry.

                              Manual: the iPad run, over the tailnet, with the teardown step
                              (tailscale serve --https=443 off) exercised afterwards so the runbook is tested rather than
                              written.



                              Required scope beyond the plan — #228, inlined by the architect

                              Phase 12's approved plan predates phase 11's partial merge and mentions none of the following.
                              All four are required here. Read #228 in full; this is the summary, not the substitute.

                              1. The static mount. Tower (or a gateway) serves /client/ and proxies /m/<id>/, the way it
                                already serves /v2/, including the frame-ancestors header a meta CSP cannot carry. Add
                                apps/client to the root build script. Today scripts/serve.mjs is the only server for the
                                built bundle.

                              2. Criterion 3 — a production t3codeSnapshot provider. Phase 11 left it unwired and unticked,
                                with spec-146-phase-11-production-wiring.test.ts asserting initAgentRoutes passes no provider.
                                Wiring one makes that test fail; that is intended. The reason it was deferred is real:
                                t3codeSnapshot is synchronous and a t3 connection is not, so this needs a cached background
                                subscription plus per-workspace t3 config Tower does not currently hold. Without it every
                                non-blocked row renders UNKNOWN.

                              3. Criterion 9b — durable asynchronous gate approval.handleGateApprove sets
                                refuseIfChecksWouldRun: true, so any ordinary project whose phase has checks is refused and the
                                operator must leave the client for the CLI. Do not fix this with a request timeout. A client
                                that gives up does not stop porch, so a timeout abandons a call that goes on to approve the gate
                                anyway — reporting one outcome while another happened. Submit, poll, report.

                              4. An operator pairing entry point.PairingStore.issue() has two callers: a dev script and
                                tests. There is no afx command a human can run, so the approval path is test-reachable only —
                                9b is unreachable in production even with async approval. Shape named in Spec 146 phase 11 partial: criteria 3 and 9b unmet, and no operator pairing entry point #228: afx pair issue/list/revoke, authority recorded from the invoking operator, token printed once to a
                                terminal and never to a log or argv. Revocation currently requires a credential the operator
                                does not hold
                                (DELETE /api/agent/v1/machines/<id> answers 401 MACHINE_CREDENTIAL_REQUIRED),
                                so afx pair revoke must work for someone holding nothing. Minting is cheap and revoking is
                                privileged; the asymmetry runs the wrong way.

                              Architect notes

                              Stack matches apps/v2 and apps/client: React 19, Vite 6, Vitest 4, Playwright.

                              A green suite cannot detect design infidelity. Spec 83 shipped a client passing 127 client
                              tests, 3,394 server tests and 15/15 Playwright — correct tokens, correct containment — that was
                              unusable, because it had dropped every label prefix, the header bar, and rendered idle sparklines as
                              invisible dots (#112). Phase 11's own three catches were the same: unstyled raw HTML from CSP
                              blocking Vite's style tag, every builder reading builder/builder-air-220, and an approval
                              confirmation that flashed and vanished. Open it and look before calling anything done.

                              Suites, because a green run is evidence only about what it ran. Five exist:
                              packages/codev unit, its vitest.e2e.config, its vitest.cli.config, apps/client unit, and
                              apps/client Playwright. apps/client/__tests__/suite-coverage.test.ts derives that table from the
                              configs and test.yml and fails if it drifts — extend it rather than hand-editing a list.

                              Phase 11's known limits, carried: wire types are hand-mirrored in
                              apps/client/src/connection/types.ts rather than shared via @cluesmith/codev-types;
                              tools/codev-agent-host imports packages/codev by deep relative path; the snapshot ships
                              protocol.statuses at 98% of payload that no client reads (#225).

                              Do not enable thread-backing on any workspace that receives porch --no-enter gate
                              notifications.
                              A thread-backed architect receives no gate notice — correct for a composer-less
                              transport, and an open decision.

                              Activity

                              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions