Skip to content

Commit fdd2bdf

Browse files
authored
Merge pull request #9 from pylon-code/upstream/2026-08-12-batch
feat: adopt the 2026-08-12 upstream batch (11 of 12 change sets)
2 parents 274500c + 7506bdb commit fdd2bdf

37 files changed

Lines changed: 1229 additions & 255 deletions

.agents/upstream-review.md

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
remote: t3code-upstream
33
branch: main
4-
reviewed-through: "c196f422ed387a1cc2cdb671b0472782e5610339"
4+
reviewed-through: "b73232bdd31e83914a8a943960c7dc4b6390b39b"
55
reviewed-through-date: "2026-08-12"
66
---
77

@@ -699,11 +699,171 @@ function that silently returns nothing for some file queries. It fails as a
699699
false negative, not an error, which makes "I searched and found nothing" claims
700700
unsafe. Use `/usr/bin/grep` when a negative result is load-bearing.
701701

702+
## 2026-08-12 (eighth batch) — `c196f422ed387a1cc2cdb671b0472782e5610339..b73232bdd31e83914a8a943960c7dc4b6390b39b`
703+
704+
Twelve upstream commits, twelve independent change sets — no dependency chains
705+
this round. Eleven adopted onto `upstream/2026-08-12-batch`, one skipped. Ten
706+
cherry-picked clean; only `O1` conflicted.
707+
708+
The deferred register was empty going in, so there was nothing to re-evaluate,
709+
and nothing new was deferred — it stays empty.
710+
711+
Upstream is in polish mode: nine of the twelve are `fix`, mostly single-file
712+
alignment and overlap repairs. `git merge-tree` against `pylon` predicted the
713+
conflict set up front, which made the split between clean picks and manual work
714+
cheap to decide.
715+
716+
**`O1` is a product-direction change wearing a `feat` label.** It collapses the
717+
sidebar footer's three labeled rows into one icon row and turns the update pill
718+
into a round icon button that doubles as Check for updates. Ported by hand
719+
because it collided with Pylon three ways: it deletes `useCanGoBack` and makes
720+
Back always navigate to `/`, **reverting Pylon's own fix** for Back landing
721+
somewhere the user never came from; Pylon's `SidebarAccountDrainPill` has no
722+
upstream counterpart and had to stay in the footer stack; and the file also
723+
holds `PylonMark`, so a careless resolution was a branding risk. Upstream's
724+
removal of "Dismiss until next launch" was taken along with it — the pill is no
725+
longer a full-width banner competing with the sidebar, so there is nothing left
726+
to dismiss. `SidebarUpdatePill.tsx` was still byte-identical to upstream's
727+
parent, so that half was taken wholesale rather than merged.
728+
729+
`animate-spin` on the refresh icon was kept rather than converted to DotMatrix.
730+
It runs only while a check is actually in flight, so it is a bounded progress
731+
indicator rather than the continuous idle repaint Pylon's motion rules forbid
732+
(contrast `E3`, where upstream's forever-pulsing `animate-status-pulse` dot
733+
_was_ replaced).
734+
735+
**`O7` shipped with a real defect, fixed on this branch rather than carried
736+
in.** The new right-panel launcher claims bare B/T/F/D/P/A on a capture-phase
737+
`window` listener and treated an empty contenteditable as "not typing". Pylon's
738+
composer is a Lexical `ContentEditable` that is empty at rest, so with a thread
739+
open and the right panel empty, a message starting with any of those six
740+
letters lost its first keystroke to a surface opening instead — and
741+
`stopPropagation` meant nothing downstream could recover it. A focused text
742+
surface now always keeps its own keystrokes; the shortcuts still work when
743+
focus is outside a text surface, which is the case the feature is for.
744+
745+
`O9` is a genuine upstream bug fix (`#5051`) but **inert in Pylon** until Pylon
746+
owns a Clerk application — `E21` was skipped precisely so a fresh clone does not
747+
point at T3 infrastructure. Taken for drift reduction in shared code. Its added
748+
prose landed in a section Pylon had not rewritten, so the "Pylon Connect"
749+
rebranding and the removed `.env.example` recipe both survived; the bare `#5051`
750+
was qualified as an upstream issue link so it does not read as a Pylon issue.
751+
752+
Verification: typecheck clean across web, mobile, shared, and contracts. The
753+
server package reports **0 errors** — only `effect` diagnostic _suggestions_, all
754+
in files this batch does not touch. Note the pre-existing
755+
`HostPowerMonitor.ts(69,9)` error recorded under the 2026-08-07 second batch is
756+
gone; something since then fixed it. Tests: **web 2324 (244 files)**, **mobile
757+
657 (105 files)**, shared `connectAuth` 7, server `publicConfig` 12 — all
758+
passing. `vp lint` clean over all 33 changed TypeScript files and
759+
`vp fmt --check` clean over all 36 changed files.
760+
761+
`O11`'s bundled test asserts ≥4.5:1 contrast across every built-in palette, and
762+
it **passes against Pylon's F10-tuned palettes** rather than only upstream's —
763+
that was the open question when this was recommended, and it is closed. `O2`'s
764+
test reads `index.css` and regex-matches it; Pylon's `index.css` diverges
765+
(DotMatrix keyframes replaced `status-pulse`), but both markers it slices
766+
between survive, and it passes.
767+
768+
**Verified in real clients.** A web pass ran against a `VACUUM INTO` copy of the
769+
developer's real database (9 threads), and an iOS pass ran on a purpose-booted
770+
iPhone 17 Pro simulator. Migrations 37–42 applied cleanly over that real
771+
database on first boot. Six change sets were confirmed against live behavior
772+
rather than only by test:
773+
774+
- **O1** — the footer is one 48px row (`flex-direction: row`) with three 32px
775+
icon buttons, replacing three stacked labeled rows. Crucially, **the
776+
Pylon-first Back behavior was confirmed**: from a thread, Usage → Back
777+
returned to `/cec0464d…/6531739c…`, the exact thread, not `/`. Upstream's
778+
version would have landed on root. On a footer page the row correctly
779+
collapses to a single Back.
780+
- **O2** — at the sidebar's 208px minimum, `.sidebar-brand` computes to
781+
`display: flex` and renders 58px wide. The retired 13.5rem (216px) container
782+
gate would have hidden it there.
783+
- **O5** — the model picker's glyphs sit at x=294 against prompt text at x=293,
784+
a 1px delta; the button box still extends 10px further left for the hit
785+
target, which is the intent.
786+
- **O7** — the launcher renders with its six Kbd badges, and availability
787+
gating is correct: `data-surface-launcher-keys="TFDA"` on a non-desktop
788+
client with no PR, so Browser and Pull request stay visible-but-disabled
789+
without claiming their letters. **Both guard paths were then confirmed**
790+
with the empty composer focused, `t` typed into the composer and no surface
791+
opened; with focus on the launcher, `t` opened the Terminal. That is exactly
792+
the defect described above and its fix.
793+
- **O12** — double-clicking the rail cleared the persisted width (`208`
794+
`null`) and reset the live sidebar 208px → 256px.
795+
- **O3** — a real end-to-end round trip on iOS. Long-pressing a thread row
796+
showed the native menu as Un-settle / **Regenerate title** / Delete, and
797+
tapping it regenerated the title through the provider: _"Fork T3 Code With
798+
Pylon Branding"__"Build and Update Pylon Desktop Fork"_, with
799+
`title_regeneration_started_at` returning to `null` and the new title
800+
rendering in the list.
801+
802+
Still unproven, each with the reason:
803+
804+
- **O6 cannot be verified on this host at all.** It is gated to
805+
`Platform.OS === "android"`, so on iOS `includeOrderedLists` is `false` and
806+
the changed path is inert — an iOS pass gives it zero coverage. There is no
807+
Android SDK or `adb` on this machine. It rests on its unit tests until
808+
someone runs an Android emulator.
809+
- **O11 and O8 are structurally unreachable locally.** `window.Clerk` is
810+
`undefined` because Pylon has no Clerk application (see `E21`), and the
811+
hosted-static onboarding route does not exist in local mode. Both need a
812+
hosted deployment, not a better test.
813+
- **O10 was not reached.** Only one thread in the real database carries
814+
file-edit activities (80 of them), its earlier turns sit behind `E10`'s
815+
pagination, and the thread is heavy enough that driving it wedged the
816+
automation bridge twice. It rests on its class-level unit test.
817+
- **O1's update pill was not exercised.** `SidebarUpdatePill` returns `null`
818+
outside Electron, so the round icon button, its checking spinner, and the
819+
release-notes tooltip need a desktop pass.
820+
821+
Incidental confirmation: `E10`'s "Load earlier turns" header renders on the
822+
large thread, and `F10`'s themes plus this batch's palette work coexist without
823+
a contrast regression.
824+
825+
Tooling note: `vp lint` exits **0 even when it reports warnings**, so its exit
826+
code proves nothing. This session confirmed the command reports real findings by
827+
feeding it a deliberate unused variable before trusting a clean result on the
828+
changed files — worth repeating rather than reading silence as success. Separately,
829+
`vitest` parses a leading-dash test filter (`-chatIndexTitlebar`) as a CLI flag
830+
and dies; drop the dash.
831+
832+
Mobile-environment notes for the next iOS pass, both of which cost time here:
833+
834+
- **The mobile dev bundle id is Pylon-owned: `com.rynfar.pylon.dev`**, not the
835+
`com.t3tools.t3code.dev` that `test-pylon-mobile` documents. The URL scheme
836+
`t3code-dev://` _is_ still compatibility-named and works. Probing for the
837+
T3 bundle id reports "no dev client installed" on a simulator that has one.
838+
- **`ios/Pods` goes stale whenever `vp i` changes a pnpm patch hash.** The Pods
839+
project hardcodes absolute store paths, so the build fails with "Build input
840+
files cannot be found" pointing at a `patch_hash=` directory that no longer
841+
exists (here `@react-native-menu/menu` moved `5ea3ae4bf…``c7f66d121…`).
842+
`pod install` fixes it and touches no tracked files. On this host CocoaPods
843+
1.17.0 additionally crashes under Ruby 4.0.6 with `Unicode Normalization not
844+
appropriate for ASCII-8BIT` unless `LANG`/`LC_ALL` are set to a UTF-8 locale.
845+
846+
| Change set | Upstream | Decision | Pylon reference | Rationale or revisit condition |
847+
| ---------- | --------------------- | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
848+
| O1 | `52e5a75a8` / `#6210` | adopted | `b0b11d966` | Sidebar footer compaction; update pill becomes a round icon button that also checks for updates. **Manual port** — kept Pylon's history-preferring Back and `SidebarAccountDrainPill`, dropped upstream's now-pointless dismiss. Revisit if the icon-only footer proves less discoverable than the labeled rows. |
849+
| O2 | `560d4a456` / `#6246` | adopted | `fecd9adb9` | The sidebar brand stopped rendering at the sidebar's own minimum width. Pylon's mark plus "Pylon" is narrower than T3's wordmark, so removing the container gate is strictly better here. |
850+
| O3 | `d37a9b09b` / `#6253` | adopted | `bda835962` | Mobile gains thread title regeneration, closing a real multi-surface gap — web and the server capability (`threadTitleRegeneration`) already shipped it. Touches `docs/user/thread-sidebar.md`, which is the right home. |
851+
| O4 | `63e6faef6` / `#6259` | skipped | `` | Vouches a T3 contributor in `.github/VOUCHED.td`. T3 contributor governance, no Pylon meaning. Same class as `F24` and `G4`. |
852+
| O5 | `5a8461480` / `#6252` | adopted | `60fdedec0` | Composer model picker aligns with prompt text. Landed inside `ChatComposer.tsx`, a deliberate Pylon divergence since `C14`, but merged clean. |
853+
| O6 | `e1378a1f4` / `#6154` | adopted | `6304fa2b9` | Android ordered lists stop escaping user bubbles. Gated to Android, where the shrink-to-fit layout bug lives. |
854+
| O7 | `b54bfc931` / `#6258` | adopted | `bbcb22eb4`, `560436ebd` | Right-panel empty state becomes a keyboard-first card launcher. Adopted, then its shortcut guard fixed on this branch — see the note above. Revisit if bare-letter shortcuts collide with anything else that lands in the panel. |
855+
| O8 | `6fd088af9` / `#6293` | adopted | `773487b9a` | The hosted onboarding header uses the shared `workspace-topbar` geometry instead of its own padding. |
856+
| O9 | `849bac894` / `#6285` | adopted | `59fe87506` | CLI OAuth parameters survive Clerk's sign-in redirect; the loopback flow routes through the hosted `/connect` page and rejects a corrupted port rather than silently downgrading. **Inert until Pylon owns a Clerk application** — taken for drift reduction. Revisit as part of any Pylon-owned Connect work, alongside `E21`. |
857+
| O10 | `e321667b1` / `#6314` | adopted | `31258d334` | The changed-files header stops overlapping its own controls; `sm:` breakpoints become `@[24rem]/changed-files` container queries, which is correct for a panel that is not viewport-width. |
858+
| O11 | `f131228a5` / `#6300` | adopted | `69acfbafc`, `7725de30a` | Clerk sign-in and profile surfaces inherit the live theme palette through CSS variables, so theme changes reach portaled Clerk UI without a remount. Its contrast test passes against Pylon's F10-tuned palettes. The doc comment's "T3 Code palette" was rebranded. |
859+
| O12 | `b73232bdd` / `#6320` | adopted | `78e415f87` | Double-clicking the sidebar rail resets its width — the reverse of drag-to-resize. Keeps upstream's `console.error` in the reset's catch; it is an error report rather than debug output, but flagged since the repo bans stray console calls. |
860+
702861
## Deferred register
703862

704-
_The register is currently empty: DEF-1 and DEF-2 were adopted on 2026-08-11
705-
(see the sixth batch above). Entries are removed once adopted or skipped, so an
706-
empty register means nothing is waiting._
863+
_The register is currently empty. DEF-1 and DEF-2 were adopted on 2026-08-11
864+
(see the sixth batch above), and the 2026-08-12 eighth batch deferred nothing
865+
new. Entries are removed once adopted or skipped, so an empty register means
866+
nothing is waiting._
707867

708868
Upstream work that has been reviewed and consciously _not_ adopted yet, with
709869
the condition that should trigger a fresh look. Entries stay here until they

apps/mobile/src/features/home/HomeRouteScreen.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export function HomeRouteScreen() {
4545
pinThread,
4646
unpinThread,
4747
movePinnedThread,
48+
regenerateThreadTitle,
4849
unsettleThread,
4950
} = useThreadListActions();
5051
const pendingTasks = usePendingNewTasks();
@@ -186,6 +187,7 @@ export function HomeRouteScreen() {
186187
onPinThread={pinThread}
187188
onUnpinThread={unpinThread}
188189
onMovePinnedThread={movePinnedThread}
190+
onRegenerateThreadTitle={regenerateThreadTitle}
189191
onEnvironmentChange={setSelectedEnvironmentId}
190192
onProjectChange={setSelectedProjectKey}
191193
onOpenSettings={() =>

apps/mobile/src/features/home/HomeScreen.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ interface HomeScreenProps {
115115
thread: EnvironmentThreadShell,
116116
direction: "up" | "down",
117117
) => Promise<boolean>;
118+
readonly onRegenerateThreadTitle: (thread: EnvironmentThreadShell) => Promise<boolean>;
118119
readonly onSelectPendingTask: (pendingTask: PendingNewTask) => void;
119120
readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void;
120121
readonly onNewThreadInProject: (project: EnvironmentProject) => void;
@@ -538,6 +539,12 @@ export function HomeScreen(props: HomeScreenProps) {
538539
},
539540
[props.onUnpinThread],
540541
);
542+
const handleRegenerateThreadTitle = useCallback(
543+
(thread: EnvironmentThreadShell) => {
544+
void props.onRegenerateThreadTitle(thread);
545+
},
546+
[props.onRegenerateThreadTitle],
547+
);
541548
const handleDeleteThread = props.onDeleteThread;
542549
const handleUnsettleThread = props.onUnsettleThread;
543550
// The settled tail renders in pages; expansion resets when the filter
@@ -615,6 +622,15 @@ export function HomeScreen(props: HomeScreenProps) {
615622
}
616623
return supported;
617624
}, [serverConfigs]);
625+
const titleRegenerationEnvironmentIds = useMemo(() => {
626+
const supported = new Set<EnvironmentId>();
627+
for (const [environmentId, config] of serverConfigs) {
628+
if (config.environment.capabilities.threadTitleRegeneration === true) {
629+
supported.add(environmentId);
630+
}
631+
}
632+
return supported;
633+
}, [serverConfigs]);
618634
// Canonical arranged pinned order (reorder-capable threads only) for the
619635
// Move up/down position flags. Computed from all shells, not the rendered
620636
// list, so search/scope filtering never disables or misdirects a move.
@@ -811,6 +827,8 @@ export function HomeScreen(props: HomeScreenProps) {
811827
onSelectThread={props.onSelectThread}
812828
onDeleteThread={handleDeleteThread}
813829
onArchiveThread={props.onArchiveThread}
830+
onRegenerateThreadTitle={handleRegenerateThreadTitle}
831+
titleRegenerationSupported={titleRegenerationEnvironmentIds.has(thread.environmentId)}
814832
settlementSupported={settlementEnvironmentIds.has(thread.environmentId)}
815833
onSettleThread={handleSettleThread}
816834
snoozeSupported={snoozeEnvironmentIds.has(thread.environmentId)}
@@ -842,6 +860,7 @@ export function HomeScreen(props: HomeScreenProps) {
842860
arrangedPinnedKeys,
843861
handleMovePinnedThread,
844862
handlePinThread,
863+
handleRegenerateThreadTitle,
845864
handleSettleThread,
846865
handleSnoozeThread,
847866
handleUnpinThread,
@@ -863,6 +882,7 @@ export function HomeScreen(props: HomeScreenProps) {
863882
snoozeEnvironmentIds,
864883
threadListV2Items,
865884
threadSearchMatchByKey,
885+
titleRegenerationEnvironmentIds,
866886
toggleSettledShelf,
867887
toggleSnoozedShelf,
868888
v2ProjectTitleByProjectKey,
@@ -967,6 +987,8 @@ export function HomeScreen(props: HomeScreenProps) {
967987
searchQuery={props.searchQuery}
968988
onArchiveThread={props.onArchiveThread}
969989
onDeleteThread={props.onDeleteThread}
990+
onRegenerateThreadTitle={handleRegenerateThreadTitle}
991+
titleRegenerationSupported={titleRegenerationEnvironmentIds.has(thread.environmentId)}
970992
onSelectThread={props.onSelectThread}
971993
onSwipeableClose={handleSwipeableClose}
972994
onSwipeableWillOpen={handleSwipeableWillOpen}
@@ -988,6 +1010,7 @@ export function HomeScreen(props: HomeScreenProps) {
9881010
[
9891011
handleSwipeableClose,
9901012
handleSwipeableWillOpen,
1013+
handleRegenerateThreadTitle,
9911014
projectCwdByKey,
9921015
props.onArchiveThread,
9931016
props.onDeletePendingTask,
@@ -998,6 +1021,7 @@ export function HomeScreen(props: HomeScreenProps) {
9981021
props.searchQuery,
9991022
props.savedConnectionsById,
10001023
threadSearchMatchByKey,
1024+
titleRegenerationEnvironmentIds,
10011025
updateGroupDisplay,
10021026
],
10031027
);

0 commit comments

Comments
 (0)