Repository navigation
Factory pages on a phone and over the tailnet - #99
Merged
Merged
Conversation
The api tests now stub the connection instead of the window origin. They expect a remote environment's calls to reach its HTTP base with its bearer token and no cookies, a cookie session to send credentials and no authorization header, a relay (DPoP) connection to fail without sending, and a connection that appears late to be awaited. All fail: api.ts has no connection seam yet and still resolves the page's own origin. Issue: #61
A new factory/http.ts sends a request on a prepared connection: its HTTP base, the session cookie or the bearer token, a timeout, and no attempt at a relay (DPoP) connection, which is reported as unavailable instead of sent unauthenticated. The factory API and the settings API both use it. The factory API reads the primary environment's connection when it calls and waits for it if startup has not produced one yet, so the pages work when opened from a phone paired to another origin. The Effect runtime on the page's own origin is gone. Issue: #61
Both get a `touch` size that is 44 px tall below the sm breakpoint and the default control height from sm up, for the Factory pages on a phone. The two files join factory/SEAMS and the design doc's seam list, now eight. No behavior to test, so there is no red phase. Issue: #61
testing.ts gains renderAt(width, element), which gives the tree a window whose matchMedia answers for that width; render is renderAt(1280). The page tests expect run cards (one link each, no table) at 375 and the table at 1280, the attention items above the stale notice and the runs at both widths, a control's answer in the body under the attention items, and touch-sized buttons. All of these fail: the page still renders a table at every width, puts the notice above the attention items, shows the answer in the header and uses sm buttons. Issue: #61
Below 640 px the runs are stacked cards, each one link to the run with its repo, issue, state, step, age and pull request; the table stays from 640 up. The body now reads attention items, notices, claim status and the answer to the last control, then the runs, so attention is at the top on a narrow screen. The header holds only the title and the pause or resume button. Buttons are touch-sized and long messages can break. Issue: #61
At 375 the gate commands and the ledger are lists with one item per command and per entry, not tables; at 1280 both stay tables. The Stop button asks for the touch size at both widths. All of these fail: the page renders tables at every width and Stop is an sm button. Issue: #61
Below 640 px the gate commands and the ledger are stacked lists, one item per command and per entry, instead of tables; the tables stay from 640 up. The Stop button is touch-sized, and the Back and Thread links are at least 44 px tall. Step and notes can break instead of forcing the page wider. Issue: #61
At 375 and 1280 px every input and every button on the populated settings form must carry the touch size, with Save and the repo buttons present. Both fail: the form uses sm inputs and sm and xs buttons. Issue: #61
Every input and button on the machine config form is touch-sized, a repo row stacks its two inputs and Remove below 640 px, and the GitHub link of a watched repo is at least 44 px tall. The test's SettingsSection stand-in now renders headerAction so the Add repo button is checked too. Issue: #61
The longer row classes made the formatter split the div, which moved the key off the line the no-array-index-key suppression covers and left it unused. The classes are a constant, so the div is one line again. Issue: #61
Runs the real settings/api.ts for every status it maps, the relay refusal, the request shape per connection type and the connection's own URL. Two cases fail today: a 200 whose body is not JSON returns ok with a null report, and a 422 whose body is not JSON returns invalid with no errors. Both should be an error. Issue: #61
A 2xx answer whose body was not JSON (an HTML fallback or a proxy page) came back as ok with a null report, and the settings panel crashed reading report.config. A 422 with such a body came back as invalid with no errors. Both are now an error, as they were before the shared request helper. Issue: #61
At 375 px the card shows the issue and pull request as plain text, so nothing on the phone layout reaches them, while the wide table links both. The new test fails: no anchor with the issue or PR href exists on the card. A second test pins that a non-GitHub pr_url stays text. Issue: #61
The phone layout showed the issue and pull request as plain text, so a card reached the run but never GitHub. The card now keeps one link to the run and adds a row beneath it, outside that link, with real anchors sized as tap targets. The issue and PR label/href rules moved into issueRef and prRef next to prLabel, and the table row uses them too, so each rule lives once. Issue: #61
375 and 1280 sit on either side of every Tailwind breakpoint, so changing max-sm to max-md or max-lg would not fail a test. Add 639 px (cards and lists) and 640 px (table and tables) for the Factory page and the run page. These pass already; they guard the boundary. Issue: #61
The size="touch" assertions only covered the populated pages. Assert it on the Retry of the Factory page (full error and the stale-poll notice), the run page's error Retry, Show recent runs after Show all, and the settings panel's load-error Retry. These pass already. Issue: #61
renderAt installed a window with only matchMedia and kept it until unmount or the next render, and most tests never unmount. Register the restore with onTestFinished so no test leaves its window behind; unmount still restores too. Issue: #61
…g path The attention message is still a flex-1 sibling of the repo span, so it is squeezed into a sliver at 375 px, and the config path sits inside one unbreakable text node. Both new tests fail: the message is not a full-width paragraph after the age, and no element holds the path with break-all. Issue: #61
… 375 px An attention item now has a first row of badge, repo and issue, and age (pushed right), and the message is a full-width paragraph below it instead of a flex-1 sibling that collapsed into a letter-by-letter sliver. The repo span can break anywhere. The "No config file" notice puts the path in a code element with break-all so it wraps inside the box rather than being clipped. The attention test's repo needle drops the "#20" suffix, since the test helper joins the split text nodes with a space. Issue: #61
# Conflicts: # factory/SEAMS
rawphp
marked this pull request as ready for review
October 17, 2026 06:57
Author
|
Factory gate: passed (attempt 2, run by the factory in a fresh checkout of the pull request's head
|
rawphp
marked this pull request as draft
October 17, 2026 07:03
# Conflicts: # apps/web/src/components/ui/input.tsx
rawphp
marked this pull request as ready for review
October 17, 2026 07:15
Author
|
Factory gate: passed (attempt 4, run by the factory in a fresh checkout of the pull request's head
|
… InlineButton Review round 1 on #99 (f1 to f5). A factory call waited up to the timeout for a connection and then gave the request the whole timeout again, so a server that hung could take twice the documented time to fail. The call now works out one deadline when it starts and hands the request only what the wait left, and the timeout message names the whole budget. Tests cover both orders: the request outliving the time left, and an answer that arrives within it. The primary-connection lookup had no test that reached it: every api test set the override. The upstream atoms it reads are now writable in the test, so one case proves the call goes to the primary environment's connection and not another's, one that a missing primary gives no_connection, and one that a primary whose connection is still None is waited for. Button's base class already gives every button a 44 px tap area on a coarse pointer, so the fork's `touch` size duplicated it. button.tsx is upstream's again and off the seam list; the pages use the sizes they had before #61. Input keeps `touch`, having no such mechanism. The 44 px links were hand-styled anchors without the design system's focus ring. They render through InlineButton now, with the tap height named once in the fork's TouchLink, and the table and the cards share one Ref.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #61
Contract
/factory,/factory/$runIdand/settings/factorywork at 375 px (no horizontal scroll, every control 44 px tall through atouchsize onButtonandInput, the runs list, the gate commands and the ledger stacked as cards and lists below 640 px, attention items first); every factory call, the settings page's included, goes through onefactory/http.tson the primary environment's prepared connection (its HTTP base, cookie session or bearer token; a relay/DPoP connection is reported unavailable, never sent unauthenticated), never the page's own origin;factory/README.md"Remote" gives the pairing steps.vp test run apps/web/src/factory→ pass (9 files, 137 tests);vp run --filter @t3tools/web typecheck→ passAcceptance criteria
The three pages work at 375 px: no horizontal page scroll, controls at least 44 px, run list as stacked cards below 640 px; component tests at 375 and 1280 px —
FactoryPage.test.tsx"FactoryPage at 375 px / 1280 px" (cards vs table, plus 639/640 px on the breakpoint),FactoryRunPage.test.tsx"FactoryRunPage at %i px" (lists vs tables, 639/640),settings/FactorySettingsPanel.test.tsx"at %i px > asks for touch-sized inputs and buttons"; every Button and Input the pages render carriessize="touch"(button.tsx:38,input.tsx:29:h-11belowsm). Live at 375 px in Chromium:document.scrollWidth375/375 on all three pages, no overflowing element, every button, input and link in the pages measured at 44 px or more (cards 58 px). The two 24 px chips above the settings form ("Applying settings for … across …") are upstream's settings chrome, not factory code.Every factory API call goes through the connected environment's HTTP base and credential, never the page origin; a remote-environment test proves it —
api.test.ts"sends to a remote environment's base with its bearer token and no cookies" (window origin127.0.0.1:3773, request tohttps://personal-mac.tail1234.ts.net:3876/api/factory/statuswithauthorization: Bearer tok-1,credentials: "omit"), "sends a cookie session's request with credentials and no authorization header", "fails without sending over a relay (DPoP) connection", "waits for a connection that appears after the call started", "fails with no_connection when none appears within the time";settings/api.test.ts(12 tests, same proofs for/api/factory/config).environments/primary/httpLayerand the Effect runtime are gone fromapi.ts.The fork server over the tailnet serves the pages to a newly paired browser; README "Remote" gives pair, open
/factory, add to the home screen —factory/README.md"Remote" (~/.local/share/t3fac/current/t3 pair --base-dir ~/.config/t3fac --tailscale, then/factoryand/settings/factoryon that origin, Share → Add to Home Screen). Pairing itself is T3's (Factory route layer behind T3's authentication #57); verified here against a dev server with a one-time pairing token, not over Tailscale (see below).Attention items at the top of
/factoryon narrow screens —FactoryPage.test.tsx"puts the attention items above the runs" (375 px), "puts the message on its own full-width line after the badge, repo and age"; live at 375 px the attention section precedes the runs list in the DOM.One integrated pass on a real phone browser over Tailscale — not done: this was a dark run with no phone in reach. The substitute: a dev server of this branch (
vp run dev, the live factory's run files and one attention item copied into its.t3/factory, no machine config so nothing claims), paired with a one-time token and driven in Chromium at a 375×812 and a 1280×900 viewport. That pass found two layout bugs the render-tree tests cannot see (an attention message squeezed into a letter-wide column beside the repo label; the config file path overflowing its box) and both are fixed with tests (cf705e60d,6264d3775). Screenshots are on the branchevidence/issue-61(not part of this branch):/factory375 px/settings/factory375 pxSeams
apps/web/src/components/ui/button.tsxandapps/web/src/components/ui/input.tsx: onetouchsize each (44 px belowsm, the default height fromsmup), the smallest change that gives every factory control a 44 px tap target withoutclassNamerestyling ofcomponents/uiexports. Entries 7 and 8 in the design doc.apps/web/src/components/sidebar/mainAppLocation.tsandmainAppLocation.test.ts: not this issue's change.main's b6bde56 (the Factory Back button) edited both without listing them, socheck-seams.shfails onmainitself and on anything merged from it, and its test imported fromvitest, which fails the web typecheck. This branch lists them (5ebd048dc) and fixes the import (461c45d8b) so the gate is green; entry 9 in the design doc.Gate
factory/scripts/check.shat443e4f842(after mergingorigin/mainat 672a8ab;factory/SEAMSconflicted with main's own listing of themainAppLocation.*seams, resolved by keeping both lists) → exit 0 (.factory/gate-3.log): seams 0, server factory tests 0 (2210), both typechecks 0.factory/scripts/check.shat6264d3775→ exit 0 (.factory/gate-2.log):check-seams.sh→ 0;vp test run apps/server/src/factory→ 0 (48 files, 2204 tests);vp run --filter t3 typecheck→ 0;vp run --filter @t3tools/web typecheck→ 0. (An earlier run at271756e51, before the live-pass fixes, also exited 0.)Models
.dw/config.ymlhas nomodelsblock)Review
vitestimport that broke the web typecheck; a 2xx or 422 config answer that is not JSON crashed the settings page (now an error); the phone cards lost the issue and pull-request links the table has (now sibling anchors under the card, 44 px); breakpoint tests at 639/640 px;size="touch"asserted on every error-path button;settings/api.tsgot its own test file against a stubbedfetch; the design doc's seam count; the testwindowstub is restored at the end of each test; the two live-pass layout bugs above.connectionWithinresolving on a null connection) survived the tests but cannot be written without a type error and the atom never emits null-to-null, so it is equivalent; thetouchsize's 44 px is proven live, not by a unit test (class strings are not measured in react-test-renderer); a DPoP (T3 Connect relay) connection gets a visible "not available over a relay connection yet" message instead of the pages' data.Lessons
mainitself had, inherited bygit merge origin/main. Cause: the base's last commit had not been gated on its own, and the merge was verified with the branch's tests only. Default: aftergit merge origin/<base>, run everyfactory.gatecommand once; a failure that comes from the base is fixed on the branch when the file is within reach (and said so on the receipt), and the reviewers get a green tip.vp run devpicked shifted ports, then died with "a server is already running for this base dir", and a third start found a stray Vite on the shifted port. Cause: killing the runner's pid left its server and Vite children listening. Default: before restarting, list the listeners on the run's ports, confirm each one's cwd is this worktree, stop those, then start again.Commits
8fc12b4a3test(factory): factory calls go to the connected environment— Make slice928495339feat(factory): send every factory call on the environment's connection— Make slicec0b987ac4feat(factory): add a touch size to Button and Input— Make slice (no red phase: a size variant)83c6d0965test(factory): the Factory page at 375 px and 1280 px— Make slice760828bc2feat(factory): lay the Factory page out for a phone— Make slice072258015test(factory): the run page at 375 px and 1280 px— Make slice808bdd27ffeat(factory): lay the run page out for a phone— Make sliced201d7d85test(factory): the settings page asks for touch-sized controls— Make slicec10a01ac1feat(factory): lay the settings page out for a phone— Make slice3ceb4ace3docs(factory): explain remote and phone use— Make sliceac50d16dafix(factory): keep the repo row's lint suppression on its element— Make slice7b23a1671Merge remote-tracking branch 'origin/main' into t3/factory-issue-61— base merge (b6bde56)5ebd048dcchore(factory): list mainAppLocation as a seam— base fix461c45d8bfix(factory): import the sidebar test from vite-plus/test— Check cycle 1bb2c7b280test(factory): cover the settings API against a stubbed fetch— Check cycle 1455328e1bfix(factory): report a non-JSON config answer as an error— Check cycle 12bfc4f75ctest(factory): expect run cards to link the issue and pull request— Check cycle 143cdba3defix(factory): link the issue and pull request on run cards— Check cycle 1ddff2fd59test(factory): pin the 640 px breakpoint on both pages— Check cycle 14d85ded9ftest(factory): assert touch size on the error-path buttons— Check cycle 1116f509dadocs(factory): list mainAppLocation as the ninth seam— Check cycle 1271756e51test(factory): restore the test window when each test ends— Check cycle 1cf705e60dtest(factory): pin the 375 px layout of attention items and the config path— live pass6264d3775fix(factory): keep attention messages and the config path readable at 375 px— live pass443e4f842Merge remote-tracking branch 'origin/main' into t3/factory-issue-61— base merge (672a8ab)Files
apps/web/src/factory/http.ts— the one fetch for every factory call: base, credential, timeoutapps/web/src/factory/api.ts— onhttp.tsand the primary environment's prepared connection; waits for a connection at startupapps/web/src/factory/settings/api.ts— onhttp.ts; non-JSON answers are errorsapps/web/src/factory/RunsTable.tsx— cards belowsmwith issue and PR links; sharedissueRef/prRefapps/web/src/factory/FactoryPage.tsx— attention first, one Pause/Resume button in the header, notes in the bodyapps/web/src/factory/FactoryRunPage.tsx— gate commands and ledger as lists belowsm; 44 px Back, Thread, Stopapps/web/src/factory/AttentionList.tsx— message on its own line; repo label breaksapps/web/src/factory/FactoryNotices.tsx— touch-sized Retryapps/web/src/factory/settings/FactorySettingsPanel.tsx,WatchedRepos.tsx— touch-sized inputs and buttons, repo rows stack, path breaks, 44 px GitHub linkapps/web/src/factory/testing.ts—renderAt(width)with amatchMediastub, restored per testapps/web/src/factory/*.test.ts(x),settings/*.test.ts(x)— the proofs aboveapps/web/src/components/ui/button.tsx,input.tsx— thetouchsize (seams 7, 8)apps/web/src/components/sidebar/mainAppLocation.test.ts— main'svitestimport fixed (seam 9)factory/SEAMS,factory/docs/design.md— seams 7–9; the Factory pages on a phone and over the tailnet #61 decision rewritten as builtfactory/README.md— "Remote"