Skip to content

chore(sync): merge upstream v0.0.42 (T3O-46) - #110

Merged
brentkelly merged 1655 commits into
t3ofrom
board/t3o-46
Sep 21, 2026
Merged

brentkelly merged 1655 commits into
t3ofrom
board/t3o-46

Conversation

@brentkelly

Copy link
Copy Markdown
Owner

Syncs the fork to upstream v0.0.42. The v0.0.38 sync was squash-merged, which dropped upstream ancestry, so this branch first grafts v0.0.38 with -s ours (tree unchanged) and then merges v0.0.42 for real. Fork seams are re-applied, the fork's fgj-based Forgejo implementation is retired in favour of upstream's fj/tea one, and the runbook in docs/t3o/seams.md is updated.

Do not squash-merge. Land by hand with git merge --no-ff; a squash destroys the upstream ancestry again.

Claude Fable 5.1 via Claude Code.

🤖 Generated with Claude Code

maria-rcks and others added 30 commits September 9, 2026 20:38
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tgg#11017)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>
shivamhwp and others added 18 commits September 15, 2026 14:11
…gg#11381)

Co-authored-by: yashranaway <yashranaway@users.noreply.github.com>
…ngdotgg#8309)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
…tgg#11958)

Co-authored-by: Antony <tnybyn@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…sync (T3O-46)

PR #57 (5698920) landed the v0.0.38 upstream sync as a squash, so t3o kept
upstream's content but lost the parent pointer: merge-base with upstream sat at
0640410 (2026-08-08) and a v0.0.42 merge produced 891 conflicted files.

This is a '-s ours' merge. The tree is byte-identical to its first parent; it
only records v0.0.38 (c0995d2) as a second parent, which is truthful because
t3o's tree already contains v0.0.38 in full. With it, the v0.0.42 merge drops
to ~50 conflicted files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1,051 upstream commits. 50 conflicted files (~120 hunks) after the ancestry
graft in the previous commit; 891 without it.

Resolutions worth knowing about:

- Forgejo: the fork's fgj-based implementation (t3o-28) is retired in favour of
  upstream's fj/tea one, which landed at the same paths. 12 fork files and the
  redundant registration markers are gone. The board's two merge seams
  (mergeChangeRequest, changeRequestMergeState) are re-attached through a
  fork-owned forgejoMerge.ts built on upstream's REST `api` surface; the
  provider carries one delegating spread.
- ws.ts shell coalescing: upstream now slims window events to routing fields.
  Board deltas are built from the payload, so board events stay whole and a
  stock event keeps one bit (todosChanged). Lives in shellCoalesce.ts.
- OrchestrationEventStore: the fork's rows-scanned paging re-expressed on
  upstream's Stream.paginate.
- ProjectionState gained upsertMany; the board-aware repository routes each
  row to the database that owns its projector.
- MCP capabilities: upstream's typed requireMcpCapability, plus "board".
- Composer: upstream extracted runtimeModeConfig itself, so AccessLevelPicker
  is dropped and the board's model-row menu moved to a fork-owned
  BoardModelControlsMenu; CompactComposerControlsMenu is upstream's verbatim.
- VcsProcess: a pinned GitHub credential outranks the per-project gitenv token.
- docs/internals/scripts.md deleted upstream; accepted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Upstream's runtime pipeline now commits every projector cursor as one batch.
The board-aware repository splits it into one statement per database and
orders listAll like upstream's, so cursor snapshots compare equal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hermes has no Array#toSorted (new upstream rule), an eslint-disable went
unused, and the re-wrapped JSX needed formatting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merge-log row, the decisions taken on this sync, a never-squash landing rule
with the -s ours graft documented as the repair, refreshed marker census
(177 across 66 files) and unmarked-edit debt table, the Forgejo inventory rows
retired and the two new merge-seam rows added. docs/t3o/dev-ports.md carries
the section rescued from upstream's deleted docs/internals/scripts.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@brentkelly brentkelly left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level summary

This PR repairs the upstream ancestry the squash-merged v0.0.38 sync destroyed (an -s ours graft of v0.0.38), merges upstream v0.0.42 into t3o, re-attaches the fork's seams, and retires the fork's fgj-based Forgejo implementation in favour of upstream's fj/tea one, with the board's two merge seams rebuilt in a fork-owned forgejoMerge.ts on top of ForgejoCli.api. I reviewed the conflict resolutions (git show --remerge-diff de4c844a0), the fork's delta over v0.0.42 in the upstream-owned files, and the three follow-up commits, rather than upstream's own 1,051 commits. The resolutions are careful: the event-store paging is correctly re-expressed on rows scanned, upsertMany is routed per database and matches upstream's statement, contracts keep both sides so persisted settings still decode, and no dangling references to the deleted Forgejo/AccessLevelPicker/openPullRequestLink code remain (web and mobile typecheck clean). I found no critical problems. There are 3 improvements — the tea path drops the forge's refusal text the merge seam was written to surface, board card chats lose the model/access controls while upstream's new resting composer is at rest, and upstream's new first-run wizard exits to the threads home rather than the board — and 4 nitpicks. Reminder for landing, since it is easy to get wrong from the GitHub UI: this must be a real --no-ff merge, never a squash.

GitHub caps a PR diff at 3,000 files and this one has ~14,700 (11,700 of them vendored .repos/), so none of the files below are resolvable as inline anchors — the API rejects them with Path could not be resolved. The findings are therefore listed here, each linked to its exact lines at the reviewed commit.

Findings


apps/server/src/sourceControl/forgejoMerge.ts:66-68

Warning

Improvement (1/3): the forge's refusal text is lost whenever the server is authenticated through tea

forgejoRefusalDetail lifts Gitea's {"message": …} out of an error detail shaped … (HTTP 405): <body>, and its doc comment says ForgejoCli.api reports failures that way. That is only true of the fj branch (requestFj). The tea branch of ForgejoCli.api never includes the response body: a 4xx becomes the constant Forgejo API request failed (HTTP 405). (ForgejoCli.ts, the status >= 400 block after tea api --include). So on a tea-authenticated server a refused merge reaches the card as a bare status code, and the message this module calls "the product" — what tells a conflict from a failing required check — is gone. Classification still works because it comes from the changeRequestMergeState probe, so this is degraded output rather than a wrong verdict, but the only test (refused(405, JSON.stringify({ message … }))) exercises the fj shape and would not notice.

Suggested fix: in fail, when cause.command === "tea" and the detail carries no body, fall back to a status-specific sentence (405 → "Forgejo refused the merge; open the pull request to see what it is waiting on", 409 → conflict), or re-read the PR and derive the reason from the merge-state probe; add a test with the tea-shaped error. Correct the forgejoRefusalDetail comment either way.


apps/web/src/components/ChatView.tsx:3711-3721

Warning

Improvement (2/3): embedded (board card) chats lose the model / traits / access controls while the composer is resting

Upstream's v0.0.42 resting composer relocates those controls out of the footer and portals them into a host inside the context strip: in ChatComposer.tsx they render only when composerControlsInStrip && restingControlsHost, and the footer renders nothing for them while resting. This seam forces mountComposerContextStrip to false for chrome === "embedded", so restingComposerControlsHost stays null in a card modal, and with a null host the resting controls render nowhere. Before the merge the footer always carried them. Upstream tolerates a missing strip, so nothing crashes, but a card chat that is scrolled up / unfocused now shows no model or access-level control until the composer is focused again — a regression specific to the fork's seam interacting with new upstream behaviour.

Suggested fix: for embedded chrome, either mount a bare host div for the resting controls (without the branch/worktree strip the board deliberately hides), or pass the composer a flag that disables the resting relocation. Whichever is chosen, note it at this T3o: comment.


apps/web/src/routes/welcome.tsx:38-47

Warning

Improvement (3/3): upstream's new first-run wizard exits to the threads home, bypassing the fork's board landing

On a fresh install the fork's cold-start redirect sends / to /board; upstream's new FirstRunGate then replaces that with /welcome, and this onDone navigates to / after the cold-start flag has already been consumed. The user therefore finishes onboarding on the stock threads view. Pairing has a deliberate exit rule for exactly this (resolvePairExitTarget); the wizard arrived with this merge and has none, and docs/t3o/seams.md does not record a decision either way.

Suggested fix: decide it explicitly. Either add a one-line T3o: seam that routes the no-project exit through the same board-home helper the pairing exit uses, or record in docs/t3o/seams.md that the wizard intentionally exits to threads.


apps/server/src/orchestration/shellCoalesce.ts:80

Note

Nitpick (1/4): per-aggregate collapse discards an earlier event's todosChanged bit

Stock events collapse to the last event per aggregate, and todosChanged is a property of one specific event. A turn.plan.updated activity followed in the same window by any other event for that thread (a message delta, a session status) survives only as the later event with todosChanged: false, so boardCardThreadsShellEvents skips the refetch. This matches pre-merge behaviour, and is currently masked by the known TODO(shell-sibling-sequence) gap (the client drops the sibling delta anyway), which is why this is a nit — but it will surface the moment that TODO is fixed.

Suggested fix: when replacing a stock entry in latest, OR the bit forward: todosChanged: previous.todosChanged || event.todosChanged; add a case to shellCoalesce.test.ts.


apps/server/src/ws.ts:102-106

Note

Nitpick (2/4): fork insertion into an upstream-owned file without a T3o: marker

This import is a fork insertion into ws.ts but carries no T3o: marker, unlike the two imports below it. The same goes for the toShellStreamEvents doc comment further down, which mentions t3o-18 but not the greppable T3o: prefix. The marker census in seams.md relies on the grep.

Suggested fix: prefix both with a // T3o: line naming the reason.


apps/web/src/components/settings/BoardModelControlsMenu.tsx:23

Note

Nitpick (3/4): doc comment refers to itself

"It began as an extension of the chat composer's BoardModelControlsMenu" — a rename slip; the composer's menu is CompactComposerControlsMenu.

Suggested fix: name CompactComposerControlsMenu here.


docs/t3o/seams.md:835

Note

Nitpick (4/4): the inherited-workflows table was not updated for v0.0.42, and no longer matches the repo

This sync brings in cursor-hygiene-webhook.yml (fires on every PR open; it ran on this PR and no-op'd only because the secrets are absent — if they were ever set it would POST the PR event payload to a third-party URL) and desktop-macos-preview-publish.yml (workflow_run + pull_request_target). Neither is in the table. Separately, gh workflow list --all shows the table has drifted from reality: ci.yml, pr-size.yml and pr-vouch.yml are listed as "kept on" but are disabled, while desktop-macos-preview.yml, web-preview.yml and mobile-fingerprint-check.yml are listed as disabled but are active. The runbook also has no step telling the syncer to check for newly inherited workflows.

Suggested fix: add the two new workflows to the table (and disable them with gh workflow disable), reconcile the table with gh workflow list --all, and add a "check for new workflows" step to the sync runbook.

brentkelly and others added 2 commits September 21, 2026 13:23
… (T3O-46)

Two round-1 review findings.

`forgejoRefusalDetail` only ever saw a forge message on a server whose
credentials belong to `fj`; `tea`'s branch of `ForgejoCli.api` reports the
status alone, so a refused merge reached the card as a bare `(HTTP 405).`
A refusal status with no message is now rendered from the status.

An embedded chat (the board card modal) suppresses the composer context
strip, which also removed the host that upstream's resting composer portals
its model, traits and access controls into — so a card's chat lost them the
moment the composer rested. Embedded chrome now mounts a controls-only
stand-in for that strip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s (T3O-46)

The rest of round 1.

Upstream's new first-run wizard exited to the threads home. It opens after
the cold-start redirect has already been spent, so setting T3o up for the
first time was the one path that never saw the board; it now exits where
pairing exits. A wizard that set up a project still opens a thread in it.

A stock shell-window survivor now carries a collapsed `turn.plan.updated`'s
`todosChanged` bit, so a plan revision followed in the same window by any
other event for that thread still refetches the card's thread todos.

Also: `T3o:` markers on the two unmarked `ws.ts` insertions, a doc comment
that named itself instead of `CompactComposerControlsMenu`, and an
inherited-workflows table that had drifted from the repo in both directions
plus a sync-runbook step to stop it drifting again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brentkelly

Copy link
Copy Markdown
Owner Author

Triage — round 1

All seven findings fixed, at 7b7b796d63248026d895fee64c8b42911a640eb4. Nothing rejected.

GitHub still cannot anchor inline threads on this PR (~14,700 files vs. the 3,000-file diff cap), so the replies are here, one per finding id.


r1-i1 — Forge refusal text lost under tea — fixed

Confirmed: ForgejoCli.api's fj branch builds … (HTTP 405): <body>, its tea branch builds Forgejo API request failed (HTTP 405). and discards result.stdout entirely, so there is no body left to lift by the time forgejoRefusalDetail runs.

forgejoRefusalDetail now takes the status alongside the detail and, when no {"message"} could be lifted, renders Gitea's two merge-refusal statuses (405, 409) as sentences. Everything else keeps the envelope, so an fj failure that happens to carry an unreadable body is not made less informative. The doc comment now says which branch produces which shape, and that the machine-readable answer never came from this text.

I fixed it fork-side rather than in ForgejoCli.api's tea branch, which is where the body is actually dropped: that file is upstream-owned and currently carries no fork edit at all, and this is a board concern (the refusal text a card shows). Trading that for a nicer message is a bad deal on a file the next sync has to merge. Worth a follow-up upstream.

Tests — all three fail on the parent commit and pass here:

  • forgejoMergeState.test.ts › "renders a merge refusal from its status when no message came back"
  • forgejoMergeState.test.ts › "keeps the forge's message, and any non-refusal envelope, ahead of the status"
  • forgejoMerge.test.ts › "explains a refusal a tea-authenticated server reported as a bare status"

r1-i2 — Embedded board-card chats lose the composer controls while resting — fixed

Confirmed by reading the path: BoardCardThreadPane mounts ChatView with routeKind="server" and a thread id, so shouldUseRestingComposerLayout can return true; that makes composerControlsInStrip true, which renders the footer controls as null and portals them into restingControlsHost — which the chrome === "embedded" seam leaves permanently null. The controls render nowhere.

Took the "mount a host" option rather than disabling resting relocation: the resting layout itself is worth having in a narrow card pane, and only the destination was missing. New fork-owned apps/web/src/board/BoardRestingComposerControlsStrip.tsx is the context strip reduced to its host, reusing ComposerSurface.ContextStrip so a card's controls land where a Threads-view thread's do, and mirroring BranchToolbar's "invisible but still measurable" treatment so an unfitting row takes no space. ChatView gains one delegating line under a T3o: marker, and the existing seam comment now says where the controls went.

No unit test: the defect is a missing mount point, and proving it needs a real DOM (apps/web has no jsdom environment, and renderToStaticMarkup never fires refs). AGENTS.md also rules out rendering components to assert attributes. Flagging this as the one fix in this round that is argued rather than tested.

r1-i3 — First-run wizard exits past the board — fixed

Confirmed, and the ordering is the reason it matters: FirstRunGate sends a fresh install to /welcome, which happens after redirectColdStartToBoard has already spent the session's one redirect. So the session that sets T3o up is the only one that never sees the board.

Added resolveOnboardingExitTarget beside resolvePairExitTarget in board/boardHomeRedirect.ts (same rule, hosted-static exemption and all — D3), and welcome.tsx uses it for both of its navigate({ to: "/" }) exits under a T3o: marker.

Deliberately not changed: a wizard that set up a project still calls openNewThread(projectRef). The user asked for that project, and answering with the board would drop the thing they just made. That is now written down in the helper's doc comment rather than left implicit.

Test: boardHomeRedirect.test.ts › "lands a finished first-run wizard on the board".

r1-n1 — Collapsed todosChanged bit — fixed

coalesceShellWindow now ORs the bit forward when a stock event replaces another under the same key. Board events are untouched: they collapse per type, so a repeat already carries its predecessor whole.

Test: shellCoalesce.test.ts › "carries a collapsed plan update's todo bit onto the survivor" — fails on the parent commit, passes here.

r1-n2 — Unmarked ws.ts insertions — fixed

T3o: markers on both: the shellCoalesce.ts import block, and the toShellStreamEvents doc comment.

r1-n3 — Self-referential doc comment — fixed

Now names chat/CompactComposerControlsMenu.

r1-n4 — Workflows table drifted — fixed

The table was wrong in both directions, and worse than reported. Read off the fork (gh workflow list --all -R brentkelly/t3code-orchestrator): ci.yml, pr-size.yml and pr-vouch.yml are disabled though listed as kept on; desktop-macos-preview.yml, web-preview.yml and mobile-fingerprint-check.yml are active though listed as disabled — and they do fire, one Mobile Fingerprint Check run on board/t3o-38 sat 24 hours before being cancelled.

Rewrote the section into what is off deliberately, what is on and should not be, what is on deliberately, and what cannot fire on its own. v0.0.42 brought four new workflow files, not two: cursor-hygiene-webhook.yml and desktop-macos-preview-publish.yml as reported, plus release-desktop.yml (workflow_call-only) and windows-tests.yml (workflow_dispatch-only), which need no decision. Added the directory diff to the sync runbook.

One deliberate gap: I did not disable anything. That is a repo-settings change outside this PR's diff, and ci.yml being off is a standing decision I should not quietly reverse. The doc now names the four that still want switching off.


Checks: apps/server and apps/web typecheck clean; 800 orchestration/sourceControl + 903 board server tests, 3,280 web board/component tests; vp lint and vp lint apps/web both clean.

Landing reminder unchanged: git merge --no-ff, never squash.

@brentkelly

Copy link
Copy Markdown
Owner Author

Adjudication — round 1 (T3O-46)

Adjudicated at 7b7b796d6, which matches this PR's head. Replies are collected here rather than on
per-finding threads for the same reason rounds 1 and 2 were: GitHub caps a reviewable diff at 3,000
files and this PR carries ~14,700, so no finding's file is anchorable.

I re-derived every claim from the code rather than from the triage notes, and for each of the four
fixes that named a test I ran it against both HEAD and the parent implementation.

Verdict: 7 upheld, 0 incomplete, 0 absent.

Finding Verdict
r1-i1 Forgejo refusal text under tea ✅ fix-upheld
r1-i2 Embedded composer controls host ✅ fix-upheld
r1-i3 Onboarding exit bypasses the board ✅ fix-upheld
r1-n1 Coalesced todosChanged bit ✅ fix-upheld
r1-n2 Unmarked ws.ts insertions ✅ fix-upheld
r1-n3 Self-referential doc comment ✅ fix-upheld
r1-n4 Inherited-workflows table ✅ fix-upheld

Test evidence

Reverting only the three changed server sources to their parent state, with the new tests left in
place, reproduces the original defects:

× renders a merge refusal from its status when no message came back
    expected 'Forgejo API request failed (HTTP 405).' to contain 'refused to merge the pull request'
× explains a refusal a tea-authenticated server reported as a bare status
    expected 'Forgejo API request failed (HTTP 405).' to include 'refused to merge the pull request'
× carries a collapsed plan update's todo bit onto the survivor
    expected false to equal true

All 41 tests in those three files pass on HEAD, as do the 12 in boardHomeRedirect.test.ts.
apps/web and apps/server typecheck with zero errors (suggestion-level Effect diagnostics only,
all pre-existing on upstream lines) and vp lint reports zero errors on both.


r1-i1 — fix-upheld

The stated cause is true in the source, not merely plausible. ForgejoCli.api's tea branch
(ForgejoCli.ts:695-718) discards result.stdout entirely and ends at
`Forgejo API request failed (HTTP ${status}).`, while setting httpStatus: status — so the
status the fix now keys on is genuinely available where the message is not. forgejoRefusalDetail
still prefers an embedded {"message": …}, so an informative fj detail is never clobbered, and
the regression guard for that (keeps the forge's message … ahead of the status) is green on both
commits, as a guard should be. The doc comment now names which branch produces which shape.

One narrow behaviour change worth knowing about rather than fixing: an fj 405 whose body is
present but contains no { now yields the canned sentence instead of the raw body. Gitea answers
merge refusals in JSON, and the canned sentence is the more actionable of the two, so this reads as
an improvement.

r1-i2 — fix-upheld

This is the one fix with no test, and the stated reason for that holds up: apps/web has no DOM
test environment at all — no jsdom, no happy-dom, no testing-library anywhere in the package
or the repo — and AGENTS.md rules out rendering components to static markup to assert attributes. So
I verified it by path instead:

  • mountComposerContextStrip is false for chrome === "embedded" and the new block is gated on
    exactly that, so the two are mutually exclusive and setRestingComposerControlsHost is never
    claimed by two mounts.
  • The portal (ChatComposer.tsx:5966) needs only a non-null host with a measurable clientWidth.
    BoardRestingComposerControlsStrip supplies the same element, the same class list and the same
    data-chat-resting-composer-controls-host attribute as BranchToolbar.tsx:652-662, inside the
    same relative z-0 wrapper — so the absolute inset-x-0 hidden state resolves against the same
    containing block and keeps a prospective width.
  • Degradation matches the Threads view: resolveRestingComposerControlsLayout moves trailing blocks
    into the overflow menu before hiding anything, so a narrow card pane gets the model picker plus an
    overflow menu rather than nothing.

Residual, cosmetic and new rather than unfixed: restingControlsHaveLeadingContext is passed as
isGitRepo || showComposerEnvironmentIndicator regardless of chrome, so on a Git project an
embedded chat renders ComposerControlSeparator (ChatComposer.tsx:4897) with no leading content
before it. It is hidden below a 400px composer surface, so only a wide card pane shows a stray
leading separator. Not grounds for another round; cheap to fold into the sync step if anyone cares.

r1-i3 — fix-upheld

The ordering the fix rests on is real: createColdStartHomeRedirect spends its flag on the first
authenticated resolution wherever it lands
(boardHomeRedirect.ts:86-95), and FirstRunGate only
navigates to /welcome afterwards — so the session that sets T3o up was indeed the one that could
never reach the board. I also checked the wizard has no second way out: every exit in
WelcomeWizard.tsx (the ghost dismiss button at 1171, the post-import finish at 1270, and 1029/1039)
funnels through onDone, and both navigate calls in that handler now use the resolved target. The
hosted-static exemption is inherited from resolvePairExitTarget rather than re-stated, which keeps
D3 in one place.

r1-n1 — fix-upheld

The OR-forward is correct and correctly scoped: board events are excluded from the merge on both
sides, which matters because they key per event type and a repeat of one type already subsumes its
predecessor. Test is red on the parent with expected false to equal true.

r1-n2 — fix-upheld

Both insertions the finding named now carry markers. Checked against git diff v0.0.42 HEAD -- apps/server/src/ws.ts: every remaining + block in that file is either marked or a rewrite of an
existing upstream comment rather than an insertion, so nothing the rule covers is left bare.

r1-n3 — fix-upheld

apps/web/src/components/chat/CompactComposerControlsMenu.tsx exists at the named path.

r1-n4 — fix-upheld

I re-ran the author's verification independently rather than taking the table on trust, and every
row matches. gh workflow list --all -R brentkelly/t3code-orchestrator confirms ci.yml,
pr-size and pr-vouch are disabled_manually while Desktop macOS Preview, Web Preview,
Mobile Fingerprint Check and Forward to Cursor hygiene are active. git diff --name-status v0.0.38 v0.0.42 -- .github/workflows/ confirms exactly four new files, and I read each one's
triggers: publish-aur and release-desktop are workflow_call-only, windows-tests is
workflow_dispatch-only, and desktop-macos-preview-publish is workflow_run on the preview
workflow — all as documented. Not disabling anything was the right call: that is a repo-settings
change, outside this PR's diff.


Landing rule unchanged and worth repeating, since it is the one thing this PR cannot recover from:
git merge --no-ff, by hand, never squash.

@brentkelly brentkelly left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level summary — round 2 (re-review at 7b7b796d6)

This PR repairs the upstream ancestry the squash-merged v0.0.38 sync destroyed, merges upstream v0.0.42 into t3o, re-attaches every fork seam and retires the fork's Forgejo implementation for upstream's. This round I re-read the two fix commits since round 1 line by line, re-ran the tests they touch (41 server + 12 web, all green at this head), and swept the remaining fork delta over v0.0.42 in upstream-owned files — including the destructive multi-database snapshot/restore in serviceLauncher.ts and the SERVICE_LAUNCHER_PROTOCOL = 3 bump, both of which survived the merge intact, and the consumer of the coalesced todosChanged bit in board/rpc.ts, which reads only the bit and so is safe with the OR-forward merge. All seven round-1 findings are closed. Nothing blocks: no critical and no improvement findings this round, and one nitpick — a cosmetic side effect of the r1-i2 fix. The handful of fork edits in upstream files that still carry no T3o: marker (serviceLauncher.ts, serviceProtocol.ts, contracts/model.ts, contracts/git.ts, the DM Sans import) all predate this PR, and the plan's "mark only where a conflict forces a touch" rule covers them, so they are not raised.

Landing reminder, unchanged: a real git merge --no-ff by hand, never squash.

Status of round-1 findings

# Prior finding Status
r1-i1 🟡 Forge refusal text lost when the server is authenticated through tea ✅ closed — 405/409 rendered from httpStatus when no message was embedded; fj message still wins; tests green
r1-i2 🟡 Embedded board-card chats lose model/traits/access controls while resting ✅ closed — BoardRestingComposerControlsStrip supplies the host; mutually exclusive with the stock strip. Untested by necessity (no DOM test env). Left one cosmetic residue, see the nitpick below
r1-i3 🟡 First-run wizard exits to the threads home, bypassing the board ✅ closed — both navigate exits use resolveOnboardingExitTarget; project-created path deliberately unchanged
r1-n1 🟢 Per-aggregate collapse discards an earlier todosChanged bit ✅ closed — OR-forward on stock events only; test added
r1-n2 🟢 Fork insertions in ws.ts without a T3o: marker ✅ closed
r1-n3 🟢 Doc comment refers to itself ✅ closed
r1-n4 🟢 Inherited-workflows table stale ✅ closed — table rewritten, runbook gained the workflows diff step

Findings

GitHub still cannot anchor inline comments on this PR (~14,700 files against the 3,000-file diff cap — the API answers "Path could not be resolved"), so the round's one finding is here with a permalink.

Note

Nitpick (1/1): Embedded chat's resting controls render a leading separator with nothing before it

restingControlsHaveLeadingContext={
isGitRepo || showComposerEnvironmentIndicator
}

restingControlsHaveLeadingContext is passed as isGitRepo || showComposerEnvironmentIndicator regardless of chrome. In the Threads view that is right: the branch/environment context sits in the strip ahead of the portalled controls, and ChatComposer.tsx:4897 draws a ComposerControlSeparator between them. In an embedded card chat the new BoardRestingComposerControlsStrip holds the host and nothing else, so on any Git project the controls open with a divider that divides nothing. It is hidden below a 400px composer surface, so only a wide card pane shows it. Purely cosmetic.

Suggested fix: pass chrome !== "embedded" && (isGitRepo || showComposerEnvironmentIndicator) under the existing T3o: seam comment.

…trols (T3O-46)

restingControlsHaveLeadingContext was passed as `isGitRepo ||
showComposerEnvironmentIndicator` regardless of chrome. An embedded board
card chat mounts BoardRestingComposerControlsStrip, which holds the
relocated controls alone, so on a Git project the composer drew a
ComposerControlSeparator with nothing ahead of it — and
measureRestingComposerControls charged its width to the fixed budget.

Gate the flag on composerContextStripAllowed, the same named gate the two
neighbouring strip derivations use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brentkelly

Copy link
Copy Markdown
Owner Author

Triage — round 2

One finding this round, fixed. Pushed as 7c16d2b44.

(Reply posted as a PR comment rather than an inline thread for the same reason as rounds 1 and 2: this PR's diff is ~14,700 files against GitHub's 3,000-file cap, so every anchor is rejected with "Path could not be resolved".)


🟢 r2-n1 — Embedded chat's resting controls render a leading separator with nothing before it — fixed

apps/web/src/components/ChatView.tsx · permalink

Confirmed, and it is slightly worse than cosmetic. measureRestingComposerControls (restingComposerControlsMeasurement.ts:65-71) finds the separator by [data-resting-controls-separator] and adds separatorWidth + gap to both naturalFixedWidth and minimumFixedWidth. So above the 400px composer-surface breakpoint the embedded card pane was not only drawing a separator with nothing ahead of it — it was also charging that width to the fixed budget, pushing a block into overflow marginally earlier than it needed to. Removing the separator removes the charge, which is the correct budget for a host that carries the controls alone.

Fix chosen: gate the flag on the existing composerContextStripAllowed rather than re-spelling chrome !== "embedded" inline as the finding suggested. That constant is declared at ChatView.tsx:3717 and is already the named gate on both neighbouring derivations (mountComposerContextStrip, showComposerContextStrip), so the new line reads the same way the two decisions it belongs with already read, and a future change to what "embedded has no strip" means lands in one place. Verified it is the single declaration of that name in the file and is in ChatView's own scope, so nothing is shadowed.

restingControlsHost={restingComposerControlsHost}
// T3o: an embedded chat's host holds the controls alone, so
// there is no Git/environment context for a leading separator
// to separate them from (T3O-46).
restingControlsHaveLeadingContext={
  composerContextStripAllowed &&
  (isGitRepo || showComposerEnvironmentIndicator)
}

No test, same reason the r1-i2 fix had none and the round-1 adjudicator verified rather than waived: apps/web has no DOM test environment — no jsdom, no happy-dom, no testing-library — and AGENTS.md rules out rendering components to static markup to assert attributes. The separator's presence is a render-time branch with no extractable pure logic worth a helper. Verified by path instead: the flag has exactly one consumer, ChatComposer.tsx:4897, which renders the separator only when composerControlsInStrip && restingControlsHaveLeadingContext; non-embedded chrome keeps composerContextStripAllowed === true, so its behaviour is byte-identical to before.


Checks at 7c16d2b44: apps/web typecheck clean (0 error TS); vp lint apps/web exit 0 with no errors; vp fmt --check clean; 1,318 tests across 93 files in apps/web/src/components/chat and apps/web/src/board pass.

Landing rule unchanged: git merge --no-ff by hand, auto-merge off, never squash. A squash destroys the upstream ancestry this PR exists to repair.

Brings T3O-48's origin-pinned `gh` invocations onto the upstream-sync branch.

Two conflicts, both in source-control tests, both "each side appended its own
tests after the same anchor":

- SourceControlProviderRegistry.test.ts — v0.0.42 declares `github`/`gitlab`
  stubs on `makeRegistry` itself, so T3O-48's own `github` field and its T3o
  marker are dropped as redundant; its two origin-vs-upstream tests are kept,
  appended after upstream's. The Forgejo mock resolves to upstream's
  `listLogins` shape, and the fork's four `fgj` routing tests stay retired —
  this branch swapped fgj for upstream's fj/tea.
- GitHubSourceControlProvider.test.ts — upstream's `resolveLink` and
  read/decode-failure tests kept in place, T3O-48's five repository-pinning
  tests appended after them.

Every other file both sides touched auto-merged to exactly "sync branch plus
T3O-48's change", verified hunk by hunk: board/rpc.ts (the coalesced
`todosChanged` consumer alongside the new refresh result), supervisorReactor,
supervisorHarness.testkit, BoardCardDetail, contracts/board.ts,
client-runtime/state/board.ts and seams.md.

Checks: apps/server, apps/web, contracts and client-runtime typecheck with zero
errors; 245 sourceControl + 916 board server tests and 2,925 web board /
contracts / client-runtime tests pass; `vp lint apps/server` reports no errors
and `vp fmt --check` is clean.

Landing rule unchanged: `git merge --no-ff` by hand, never squash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brentkelly
brentkelly merged commit 6268e74 into t3o Sep 21, 2026
1 check passed
@brentkelly
brentkelly deleted the board/t3o-46 branch September 21, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.