fix(web): project monograms over two characters can no longer be saved#23208
Problem
In Project Settings → Choose icon → Monogram, entering ABC enables Save icon. Saving closes the picker, then the server rejects the value and the client shows “Failed to mutate project.” The existing limit is two graphemes.
Change
Validate the normalized input with the same grapheme counting as the server before enabling Save. The existing invalid-field state, automatic preview fallback, and save guard now reject longer monograms while leaving the dialog open for correction. Stored legacy monograms remain decodable.
Scope and approval
Fixes https://github.com/pingdotgg/t3code/issues/22793
Triaged report: #22794.
Triaged bug via the automated issue flow. This is a focused correction to the existing monogram limit, reproduced in the real web app on current main.
Verification
vp test run packages/contracts/src/project.test.ts apps/server/src/orchestration-v2/ProjectCommands.test.ts: 2 files, 9 tests passed again after merging current main.- Targeted
vp lintandvp fmt --checkforProjectIconPickerDialog.tsx: passed. node_modules/.bin/tsc --noEmit -p apps/web/tsconfig.json: passed after merging current main (Effect suggestions only).- Real web app:
ABCis invalid and Save is disabled; correction to two graphemes saves; combining-accent inputéBsaves asÉB. - Two independent code reviews (GPT-6.1 Sol and Claude Opus 5.5): MERGE, no confirmed findings.
- Fresh independent proof audit: settled static-v2 rebuild matched 0 / 1,137,920 differing pixels; both replacement diff images recomputed exactly. Earlier reconstruction failures and superseded stills are retained in the evidence history below.
- Windows/Electron unavailable on this Linux VM, unverified. Native mobile has no monogram editor; its unchanged display path was source-audited, not run. Remote/relay/tunnel paths were not run.
Evidence
Recordings below narrate the actual interaction. The tables compare the ABC state; Diff0 amplifies every nonzero RGB difference 8×, and DiffT uses threshold 16.
1440-light, viewport height 1000 CSS px
| Before | After | Diff0 ×8 | DiffT 16 |
|---|---|---|---|
| before-1440-light-static-v2.png | after-1440-light-static-v2.png | 1440-light-static-v2-whole-diff0-x8.png | 1440-light-static-v2-whole-diff16.png |
| b5a226e49c | 83c7355eec | Same pair | Same pair |
1440-dark, viewport height 1000 CSS px
| Before | After | Diff0 ×8 | DiffT 16 |
|---|---|---|---|
| before-1440-dark-abc.png | after-1440-dark-abc.png | 1440-dark-whole-diff0-x8.png | 1440-dark-whole-diff16.png |
| 70e5e384da | def9e75af2 | Same pair | Same pair |
390-light, viewport height 1000 CSS px
| Before | After | Diff0 ×8 | DiffT 16 |
|---|---|---|---|
| before-390-light-abc.png | after-390-light-abc.png | 390-light-whole-diff0-x8.png | 390-light-whole-diff16.png |
| 70e5e384da | def9e75af2 | Same pair | Same pair |
390-dark, viewport height 1000 CSS px
| Before | After | Diff0 ×8 | DiffT 16 |
|---|---|---|---|
| before-390-dark-abc.png | after-390-dark-abc.png | 390-dark-whole-diff0-x8.png | 390-dark-whole-diff16.png |
| 70e5e384da | def9e75af2 | Same pair | Same pair |
Recording steps, complete matrix, environment, differences and reconstruction measurements
Project monogram evidence
Seven required runtime rows have supporting evidence, with the corrections below; all 12 matrix rows are accounted for. Four before/after pairs plus one cleanup supplement. The 1440/light still pair and its two whole-surface diffs are replaced by the new retained static-v2 session below. Independent AFTER reconstruction has 3,204 one-level RGB differences outside the picker; strict zero-delta reproduction remains unmet. No push or PR.
Before 70e5e384da; app fix def9e75af2; fixture-only commit f0c89f6adf. Captured app code is identical to def9e75af2; the new commit adds only JSON.
Environment
Linux x86_64, Chrome/Chromium 154.0.8037.92, kernel 6.1.102. Own T3 preview tab; no primary tab used. DPR 2; viewport 1440×1000 or 390×1000 CSS pixels, 100% zoom, visualViewport.scale=1, CSS zoom=1, root font 16px. Body/input stack: -apple-system, BlinkMacSystemFont, Segoe UI, system-ui, sans-serif. Stock T3 Code theme; light/dark selected through Appearance. Desktop UA remains desktop at 390 (responsive web).
Both sides: real Settings → Projects → Choose icon → Monogram, project Proof Audit, saved KC, teal. Before uses the provided isolated seed copy; after reset from ÉB to KC through UI. No agents were run. Caption overlays narrate actions; no voice track. Readability holds do not determine pass/fail: toast arrival and reopened persisted values are observed. Timestamps approximate caption/action initiation; visible video captions are authoritative. Clock not frozen; picker contains no clock-dependent UI.
Recordings retain 2880×2000 desktop pixels. Preview snapshots auto-reduce desktop to 1280×889; 390 snapshots/recordings are 780×2000. Full-surface PNGs are primary diff inputs. Video frames are also extracted separately.
Coverage
| Row | Result | Evidence / limit |
|---|---|---|
| A1 | Verified | All four pairs: ABC enabled before → close/error/KC retained; invalid PA fallback and disabled Save after → KC save/reopen. |
| A2 | Verified | 1440/light after: B saves/reopens; literal e + combining acute + B normalizes to ÉB and saves/reopens. |
| A3 | Verified | Initial KC enabled, save/reopen; invalid ABC Cancel/reopen restores KC. |
| A4 | Optional, unverified | No synthetic legacy persisted ABC state manufactured. |
| R1 | Verified | Empty, whitespace, A!, over-32-unit single grapheme disabled; spaced lowercase ab → AB enabled. |
| R2 | Verified | Invalid ABC → Icons/Emoji enabled → Monogram disabled; Icons saves/reopens selected. No exhaustive icon-search/emoji/color permutations. |
| R3 | Verified | PA automatic preview differs from ABC/KC/ÉB; teal retained. |
| R4 | Verified | All four cells show input, hint, preview and footer without clipping. |
| R5 | Source-only | Connected editable local project exercised; grouped permissions/remote/relay/tunnel not runtime tested. |
| U1 | Unverified | Windows/Electron unavailable on Linux VM. |
| R6/U | Source-only/native unverified | Native mobile display-only consumers unchanged; no native runtime capture. |
| R7 | Source-only plus Cancel/reopen | Matrix audit identifies one picker caller, no separate palette/keybinding/provider branch. |
Actual before toast: Failed to update project icon on fs-26 / Failed to mutate project. After disabled-position click leaves dialog open with no failure toast. No separate WebSocket packet trace was captured.
Paired evidence
Whole-surface diff0 shows every nonzero absolute RGB difference amplified 8× with saturation. diff16 is white when max(|ΔR|,|ΔG|,|ΔB|)>16 in 8-bit channels, black otherwise. No cropping, masking, exclusion or alignment in primary whole-surface diffs. Picker crops/diffs are supplementary.
1440-light
BEFORE steps / approximate timestamps
- 00:00.00 — BEFORE 70e5e384da | 1440×1000 CSS | light | 100% zoom 1. Open saved teal KC. Save is enabled.
- 00:02.56 — BEFORE | 2. Type ABC. Preview ABC; Save incorrectly enabled.
- 00:07.12 — BEFORE | 3. Click Save. Picker closes; server rejects ABC. Read the failure toast.
- 00:12.18 — BEFORE | 4. Reopen. Saved KC remains unchanged.
BEFORE narrated recording 1440-light
AFTER steps / approximate timestamps
- 00:01.70 — AFTER def9e75af2 | 1440×1000 CSS | light | 100% zoom 1. Saved KC is enabled. Save and reopen: KC persists.
- 00:06.14 — AFTER | 2. Type ABC: invalid, PA fallback; disabled Save cannot close or cause a failure toast.
- 00:11.01 — AFTER | 3. Cancel invalid draft and reopen: saved KC restored. Correct to KC and save/reopen.
- 00:26.80 — AFTER | 4. One grapheme B: accepted. Save and reopen B.
- 00:32.36 — AFTER | 5. Decomposed e + acute + B: normalized ÉB accepted. Save and reopen ÉB.
- 00:38.37 — AFTER | 6a. Empty: invalid/disabled.
- 00:40.66 — AFTER | 6b. Whitespace: invalid/disabled.
- 00:42.88 — AFTER | 6c. Punctuation A!: invalid/disabled.
- 00:45.10 — AFTER | 6d. Whitespace + lowercase ab: normalized AB, enabled.
- 00:47.59 — AFTER | 6e. 34 input code units; 33 after NFKC, one grapheme: invalid/disabled.
- 00:49.83 — AFTER | 6f. ABC: invalid/disabled.
- 01:09.74 — AFTER | 7. Switch to Icons: Save enabled despite invalid monogram draft.
- 01:13.82 — AFTER | 7. Switch to Emoji: Save enabled despite invalid monogram draft.
- 01:17.05 — AFTER | 7. Switch to Monogram: ABC remains invalid; Save disabled.
- 01:20.34 — AFTER | 7. Switch to Icons: Save enabled despite invalid monogram draft.
- 01:23.61 — AFTER | 8. Save Icons; reopen: Icons selected, no Letters field. Restore teal KC. The original clip ends after Save; its promised final cleanup reopen is supplied by the supplement below.
AFTER narrated recording 1440-light
Replacement static session v2 — 2026-10-22, separate from the recordings above
These are new real-app captures, not newly labeled old baseline images or frames from the recordings. BEFORE is current origin/main b5a226e49c; AFTER is current task head 83c7355eec. The picker source is unchanged between the original baseline and current main, and between the original fix and current head. Baseline runs its own genuine checkout/server; no reverse patch or app edit was used.
BEFORE — ABC preview and enabled Save
Before 1440-light static v2: real current-main app, ABC accepted
AFTER — PA fallback, invalid border and disabled Save
After 1440-light static v2: real current-head app, ABC invalid
Whole surface: zero threshold, 8× amplified
1440-light static v2 whole-surface difference at zero threshold
Whole surface: threshold 16
1440-light static v2 whole-surface difference above threshold 16
Whole-surface pixels: 7,581/1,137,920 nonzero; 3,639 above 16. Lossless snapshots are 1280×889, the preview tool's automatic reduction of the whole 1440×1000 CSS viewport at DPR 2. No crop, mask, alignment, caption overlay, caret hiding CSS, font injection or screenshot resizing. Letters was blurred through the browser before both stills. No unrelated toast is present in either still. Settings uses a neutral project with one checkout at the identical path, so checkout metadata and thread timestamps are absent from the background. Sidebar width is explicitly 256 CSS px. Stock T3 Code light theme, environment identification None through the supported Appearance setting, contrast 100%, glass opacity 80%, interface font 16px, zoom/visual scale 1.
Historical recording-derived supplements below belong to the old session, including the picker crops. They were not replaced and are not crops or frames of static-v2.
BEFORE ABC frame extracted from recording at 00:06.15.
AFTER ABC frame extracted from recording at 00:08.41.
Supplementary before crop · After crop · Cropped diff0 ×8 · Cropped diff16
1440-dark
BEFORE steps / approximate timestamps
- 00:00.00 — BEFORE 70e5e384da | 1440×1000 CSS | dark | 100% zoom 1. Open saved teal KC. Save is enabled.
- 00:02.56 — BEFORE | 2. Type ABC. Preview ABC; Save incorrectly enabled.
- 00:06.62 — BEFORE | 3. Click Save. Picker closes; server rejects ABC. Read the failure toast.
- 00:10.90 — BEFORE | 4. Reopen. Saved KC remains unchanged.
BEFORE narrated recording 1440-dark
AFTER steps / approximate timestamps
- 00:00.00 — AFTER def9e75af2 | 1440×1000 CSS | dark | 100% zoom 1. Saved KC. Enter ABC: invalid, PA fallback, Save disabled.
- 00:03.32 — AFTER | 2. Click disabled Save: stays open, no failure. Cancel/reopen: KC restored.
- 00:07.25 — AFTER | 3. Correct to KC; Save, reopen: KC persisted.
AFTER narrated recording 1440-dark
BEFORE whole-surface ABC state
Before 1440-dark: ABC accepted, Save enabled
AFTER whole-surface ABC state
After 1440-dark: ABC invalid, PA fallback, Save disabled
Whole surface: zero threshold, 8× amplified
1440-dark whole-surface zero-threshold amplified difference
Whole surface: threshold 16
1440-dark whole-surface threshold-16 difference
Whole-surface pixels: 32,077/1,137,920 nonzero; 24,805 above 16.
BEFORE ABC frame extracted from recording at 00:07.62.
AFTER ABC frame extracted from recording at 00:03.11.
Supplementary before crop · After crop · Cropped diff0 ×8 · Cropped diff16
390-light
BEFORE steps / approximate timestamps
- 00:00.00 — BEFORE 70e5e384da | 390×1000 CSS | light | 100% zoom 1. Open saved teal KC. Save is enabled.
- 00:02.58 — BEFORE | 2. Type ABC. Preview ABC; Save incorrectly enabled.
- 00:05.68 — BEFORE | 3. Click Save. Picker closes; server rejects ABC. Read the failure toast.
- 00:09.22 — BEFORE | 4. Reopen. Saved KC remains unchanged.
BEFORE narrated recording 390-light
AFTER steps / approximate timestamps
- 00:00.00 — AFTER def9e75af2 | 390×1000 CSS | light | 100% zoom 1. Saved KC. Enter ABC: invalid, PA fallback, Save disabled.
- 00:03.56 — AFTER | 2. Click disabled Save: stays open, no failure. Cancel/reopen: KC restored.
- 00:11.47 — AFTER | 3. Correct to KC; Save, reopen: KC persisted.
AFTER narrated recording 390-light
BEFORE whole-surface ABC state
Before 390-light: ABC accepted, Save enabled
AFTER whole-surface ABC state
After 390-light: ABC invalid, PA fallback, Save disabled
Whole surface: zero threshold, 8× amplified
390-light whole-surface zero-threshold amplified difference
Whole surface: threshold 16
390-light whole-surface threshold-16 difference
Whole-surface pixels: 111,529/1,560,000 nonzero; 105,460 above 16.
BEFORE ABC frame extracted from recording at 00:07.09.
AFTER ABC frame extracted from recording at 00:07.89.
Supplementary before crop · After crop · Cropped diff0 ×8 · Cropped diff16
390-dark
BEFORE steps / approximate timestamps
- 00:00.00 — BEFORE 70e5e384da | 390×1000 CSS | dark | 100% zoom 1. Open saved teal KC. Save is enabled.
- 00:02.64 — BEFORE | 2. Type ABC. Preview ABC; Save incorrectly enabled.
- 00:06.04 — BEFORE | 3. Click Save. Picker closes; server rejects ABC. Read the failure toast.
- 00:09.80 — BEFORE | 4. Reopen. Saved KC remains unchanged.
BEFORE narrated recording 390-dark
AFTER corrected recording / approximate observed timestamps
The original after clip ended after Save without its narrated final reopen. It is superseded for interaction proof by this retained correction, recorded against the same application code. The original comparison PNG and diffs below remain unchanged; they are not frames from this replacement.
- 00:00–00:02.5 — Saved teal KC open; enter ABC.
- By 00:16 — ABC invalid, PA fallback, Save disabled. Attempt the disabled Save position; picker stays open without failure toast.
- By 00:32.5 — Cancel and reopen: KC restored. Enter ABC again, correct to KC.
- Around 00:37.5 — Save closes the picker.
- By 00:38.5 through 00:41 — Actually reopened: Letters KC, teal preview, enabled Save held visibly.
AFTER corrected 390-dark recording
After 390-dark: actual final reopen of saved teal KC
BEFORE whole-surface ABC state
Before 390-dark: ABC accepted, Save enabled
AFTER whole-surface ABC state
After 390-dark: ABC invalid, PA fallback, Save disabled
Whole surface: zero threshold, 8× amplified
390-dark whole-surface zero-threshold amplified difference
Whole surface: threshold 16
390-dark whole-surface threshold-16 difference
Whole-surface pixels: 111,520/1,560,000 nonzero; 106,563 above 16.
BEFORE ABC frame extracted from recording at 00:07.26.
AFTER ABC frame extracted from recording at 00:04.31.
Supplementary before crop · After crop · Cropped diff0 ×8 · Cropped diff16
1440-light cleanup supplement
The original 1440/light clip verifies B, ÉB, invalid cases and Icons persistence, but its last cleanup Save did not include the promised reopen. This supplement saves teal Folder Code, reopens Icons, switches to Monogram KC, saves, and actually reopens KC. It supplements only cleanup; original comparisons remain unchanged.
- By 00:03.5 — Save Folder Code closes the picker.
- By 00:05.5 — Icons actually reopened; switch to Monogram, type KC.
- By 00:10 — Save KC closes the picker.
- By 00:11.5 through 00:13.5 — Actual reopened KC held visibly.
After 1440-light: actual cleanup reopen of saved teal KC
Historical session: disclosed surface differences and limits
Intended app differences: ABC→PA preview; aria-invalid false→true and error outline; enabled→disabled Save appearance. Preview glyph count/size uses the existing monogram renderer. Dialog geometry, color selection, labels, hint and footer layout otherwise unchanged.
BEFORE/AFTER caption labels, commits, step wording, wrapping and height differ. Whole-surface diffs retain this, including underlying blurred header/theme artwork revealed by shorter captions. This causes the top stripes in some diffs.
Input caret blink visibility differs between stills. Video-extracted frames can also include the preview cursor and H.264 compression/color noise. Primary diffs use lossless snapshots.
Both projects renamed Proof Audit; saved initial icon teal KC. After-only regression clip temporarily saves B, ÉB and Icons, then restores KC. Every comparison still uses ABC draft over saved KC.
Separate environment IDs, pairing sessions, ports, timestamps and checkout metadata. Before copied seed plus startup registers /tmp/issue-22794-before/apps/server as third checkout; after has two checkouts. Lower settings checkout/count text differs outside the captured picker area. The prior paired-image audit found no additional unexplained difference; this is not a claim of identical background state across independent rebuilds. Databases are not claimed byte-identical; full-surface diffs hide nothing.
Both servers emit the pre-existing migration-history divergence warning for ids 68–70 unknown to this build. Readiness and observed mutations succeed. No migration change included.
Only repository edit from test work: scripts/fixtures/project-icon-monograms.json, using actual kind discriminator. No app code, CSS, fonts, layout, timing, contracts, server, providers or native code changed. No PR assets/plans, databases or secrets committed/uploaded.
Theme/viewport selected and unrelated update notification dismissed before capture. Within pairs theme/viewport/font scale/teal/project name/initial icon match. No clock override, font injection, request interception, application style override or projection writes; only narration overlay injected.
App-state replay / historical proof state
Committed reusable browser input fixture: project-icon-monograms.json. It contains executable T3 preview seedSteps and literal Unicode cases. No real database is needed to recreate the neutral project/icon.
Create isolated base/head worktrees, install with node_modules/.bin/vp install --frozen-lockfile (fallback /home/ubuntu/t3code/node_modules/.bin/vp), then vp run dev using worktree-local .t3. Never run against ~/.t3/userdata.
For fresh neutral state: mkdir -p /tmp/t3-project-icon-proof; git -C /tmp/t3-project-icon-proof init. Add that directory through the real Add project UI, select its local editable project in /settings/projects, retain the UI-generated scope URL. Any isolated editable project also works.
Pair with startup URL or node apps/server/src/bin.ts pair --base-dir /.t3. If automatic link consumption fails, enter fresh token in Pairing token form and Continue. Never publish tokens.
Replay fixture seedSteps with this functions.exec code after selecting project settings (tabId is your own preview tab).
| 1 | const read = await tools.exec_command({cmd: "cat scripts/fixtures/project-icon-monograms.json", workdir: "/home/ubuntu/wt/issue-22794"}); |
| 2 | const fixture = JSON.parse(read.output); |
| 3 | for (const {tool, ...args} of fixture.seedSteps) { |
| 4 | const r = await tools["mcp__t3_code__" + tool]({...args, tabId}); |
| 5 | if (r.isError) throw r; |
| 6 | } |
- Reopen and confirm KC. Use Appearance to choose stock light/dark and preview_resize for 1440×1000 or 390×1000. Follow recorded steps. Do not manufacture legacy ABC using projection writes.
Retained-state inventory checked during finishing: original before ports 7292/15332 have no listener. The independent before checkout /tmp/issue-22794-proof2-before remains on disk but has no confirmed running server. At initial inspection, after listeners were web PID 134942 at 7192 (cwd /home/ubuntu/wt/issue-22794/apps/web) and backend PID 150579 at 15232 (cwd /home/ubuntu/wt/issue-22794/apps/server). At final inspection the backend was PID 242827 with the same cwd. During this pass an external actor advanced the clean worktree from the captured f0c89f6adf to 83c7355eec, a merge of origin/main; no repository mutation came from this finishing task. The running app is therefore not asserted to match the historical captured commit. These are inherited, not owned by this finishing task. No process was started or killed. New own preview tab tab_n_f0288758-9d52-4b49-93c6-2f87f67b0762 opened at the after pairing form; it was not authenticated or used to recapture evidence. Earlier tab/session/PID claims are historical and superseded by this inventory.
Artifacts: /tmp/issue-22794-evidence. captures.json records original media paths and steps. process.py rebuilds snapshots and all whole/crop diffs; extract.py finds closest ABC picker frames. Dependencies: Pillow and temporary imageio-ffmpeg at /tmp/issue-22794-tools. Final MP4s equal raw originals, no slowdown/trimming/audio synthesis.
Retained-snapshot state rebuild (not pixel-exact)
This recreates the same isolated seed lineage used for the evidence. Keep the existing proof server running; use a different disposable path for an independent rebuild. The seed stays local and is not a public artifact.
| 1 | # Run from /home/ubuntu/wt/issue-22794. |
| 2 | git worktree add --detach /tmp/issue-22794-proof2-before 70e5e384da |
| 3 | cd /tmp/issue-22794-proof2-before |
| 4 | /home/ubuntu/t3code/node_modules/.bin/vp install --frozen-lockfile |
| 5 | mkdir -p .t3/userdata |
| 6 | cp /tmp/issue-22794-evidence/seed.sqlite .t3/userdata/statev2.sqlite |
| 7 | node_modules/.bin/vp run dev |
Retain that terminal session. Read actual web/backend ports from its dev-runner line; ports depend on the path, so do not hardcode 7292 for this new checkout. Start an own preview tab with reuseExistingTab=false, navigate to that startup pairing URL, and submit a fresh token through the Pairing token form if needed. Mint a fresh token from that checkout with:
| 1 | node apps/server/src/bin.ts pair --base-dir /tmp/issue-22794-proof2-before/.t3 |
Navigate to /settings/projects. In the copied seed, click the real project row named pingdotgg/t3code with workspace /home/ubuntu/t3code. This produces /settings/projects?project=git.cafe%2Fpingdotgg%2Ft3code. Use the committed fixture replay snippet above from /home/ubuntu/wt/issue-22794, passing your own tabId on every call. Reopen Choose a project icon; wait for #project-monogram and assert its value is KC. Select Appearance → Use light mode or Use dark mode, return via Project, resize, and open Choose a project icon. All subsequent actions are listed above each pair.
For an independent after rebuild, repeat with a different worktree path and app commit def9e75af2, copying the same seed before server start. Replay the same fixture JSON from the existing after checkout. The fixture commit f0c89f6adf may also be used: its only addition is that JSON, and captured application code is identical. Do not copy auth/session files, do not reuse a consumed pairing token, and do not share a running database between servers.
Historical capture endpoints: before web 7292/backend 15332 at /tmp/issue-22794-before (now stopped); after web 7192/backend 15232 at /home/ubuntu/wt/issue-22794. Both had saved teal KC at capture time; current state must be inspected. Pair each normally if needed, select the Proof Audit project, and use its generated scope route. Keep lead-owned after server running.
Supporting files
matrix.md · transcripts.json · diff-metrics.json · video-stills.json · process.py · extract.py
FRICTION: tool — pairing auto-link did not establish session; normal token form worked. PNG auto-downscaling required coordinate correction. Unsupported locator exact attribute corrected. infra — ffmpeg absent; temporary imageio-ffmpeg under /tmp used (default pip target lacked permission). policy — unsafe-style temporary rm -rf rejected; used new extraction directory. Historical capture friction only; unresolved reconstruction limit is recorded below.
Historical session: recovered rendering recipe and unresolved reconstruction gap
The committed JSON replays app state, not a complete browser profile. The original overlay construction was recovered verbatim from the original capture task activity. Run this in preview_evaluate after opening the real picker and typing ABC; the string below is the exact BEFORE 1440/light comparison caption from captures.json:
| 1 | ((s) => { |
| 2 | let e = document.getElementById('proof-caption'); |
| 3 | if (!e) { |
| 4 | e = document.createElement('div'); |
| 5 | e.id = 'proof-caption'; |
| 6 | e.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:2147483647;background:#17202a;color:white;padding:8px 12px;font:14px/1.4 sans-serif;pointer-events:none;white-space:pre-wrap'; |
| 7 | document.body.append(e); |
| 8 | } |
| 9 | e.textContent = s; |
| 10 | })('BEFORE | 2. Type ABC. Preview ABC; Save incorrectly enabled.'); |
Use the same stock theme, CSS viewport 1440×1000, DPR 2, zoom 1, root font 16px and body font stack listed above. Preserve focus in Letters with the caret after ABC; do not freeze or hide the caret. Set scroll to the top before opening the picker. Original desktop PNGs are whole browser surfaces reduced by preview to 1280×889; MP4 is 2880×2000. Never resize the PNG to pretend it is a native-resolution screenshot.
The saved desktop surface has its blurred left sidebar boundary near PNG x=185, corresponding to approximately 208 CSS pixels at 1280/1440 scale. This is a media-derived estimate, not a recovered DOM measurement. The app's persistent width key is chat_thread_sidebar_width (a JSON finite number); a reconstruction candidate is localStorage.setItem('chat_thread_sidebar_width', '208') followed by reload. A fresh default is 256 CSS pixels. The original storage value and complete layout/profile state were not retained; 208 has not been validated as an exact reconstruction. Do not present this candidate as an exact recipe. No application CSS override is required or authorized for matching the image.
The prior independent BEFORE 1440/light rebuild matched browser version, viewport/DPR/zoom, seed and fixture. Against the delivered 1280×889 PNG it differed in 237,338/1,137,920 pixels; 53,296 exceeded threshold 16. Inside the picker 13,431/167,808 differed; only 29 exceeded 16, at the caret. Missing caption and background geometry account for substantial remaining whole-surface differences. No further independent browser rebuild was performed in this finishing step. Exact whole-surface reproduction remains unresolved, not a product-regression finding. Background artwork/layout, caption wrap/height, caret phase, environment metadata and checkout count must not be described as identical across rebuilds.
This finishing pass recomputed all eight primary diff images from retained PNG inputs; every pixel matched and the original four reported metrics matched. The replacement recordings repair interaction gaps without replacing or silently relabeling original stills/diffs. Correction capture JSON only logs the first caption; the approximate timelines above were checked against retained extracted frames, not invented complete tool transcripts.
Remaining unverified: exact whole-surface browser rebuild; optional legacy persisted ABC; grouped permissions/multi-member and remote/relay/tunnel runtime; Windows/Electron and native mobile runtime; exhaustive icon-search/emoji/color combinations; network packet-level proof of no request. Visible disabled activation/no close/no failure toast is recorded.
FRICTION: runbook — original overlay and sidebar/profile reproduction details missing; overlay recovered, sidebar estimate explicitly unvalidated. tool — new preview opened at pairing form, with transient 502/503 and unauthenticated WebSocket diagnostics; no recapture attempted. infra — original before server stopped. No app edits, commits, pushes, PRs or forge writes in this finishing pass; owned PIDs: none.
Finishing diff validation results.
Static-v2 retained recipe and independent reconstruction
The static replacement directly addresses the earlier 237,338-pixel reconstruction failure: it starts a new documented session rather than attempting to recreate the lost original sidebar/caption state. The old three other pairs and all interaction recordings remain historical and unchanged. Strict zero-delta reproduction is not claimed for static-v2 either: the one independent rebuild below measured a much smaller residual, fully reported here.
State and recipe
Retained local directory: /tmp/issue-22794-evidence. No fixture or app changes were needed in the repository; no commit, push, PR or forge mutation occurred. The JSON fixture is the existing scripts/fixtures/project-icon-monograms.json from the task checkout.
- For a clean baseline use current-main commit b5a226e49c52ecab4ddc2d678a7012d97e651a6a; for AFTER use 83c7355eec0708ba5bdc8658d974bc4c4e2a0beb. Create a separate detached worktree, run
node_modules/.bin/vp install --frozen-lockfile(the main checkout's vp executable can bootstrap this), and before starting its server copyseed.sqliteinto<worktree>/.t3/userdata/statev2.sqlite. Seed SHA-256:aff5f3292b24f8be2b3dddcb4f9763da8bcb01a4e81f4e52488b6b06482ca435. This is the retained isolated seed, not live T3 state. Startnode_modules/.bin/vp run dev; read its actual ports. Never share a running database. mkdir -p /tmp/t3-project-icon-proofandgit -C /tmp/t3-project-icon-proof initprovide the same neutral empty Git repository. Use an own T3 preview tab in the Default profile. Pair via its normal Pairing token form using a fresh token fromnode apps/server/src/bin.ts pair --base-dir <worktree>/.t3. Credentials/cookies are intentionally neither exported nor uploaded; pairing is the only environment-specific prerequisite.- The retained replay-static-v2.js contains the exact browser sequence: Add project → Local folder →
/tmp/t3-project-icon-proof; choose that project in Settings; replay the existing fixture to name it Proof Audit and save teal KC. It discovers the generated scope route rather than hardcoding environment IDs. - Pass
static-browser-seed.jsonasbrowserSeed, the existing fixture JSON asfixture, the pairedtabId, the server weboriginandtoolstoreplayStaticV2. It restores all localStorage and sessionStorage entries, resizes to 1440×1000, emulates light, reloads the generated scoped route, opens the saved monogram, enters ABC, blurs the focused input and takes a lossless whole-surface snapshot. For an existing retained project usecreateProject:falseand its retained route. Browser storage is local evidence only; it contains no pairing token, but is not needed as a public artifact. static-v2-afterTab-state.json,static-v2-beforeTab-state.jsonandstatic-v2-rebuild-state.jsonretain exact local/session storage and profile UA, focus and scroll. AFTER and rebuilt AFTER storage are exactly equal; session storage is exactly equal; every scroll offset is zero; focus is BODY. Sidebar useschat_thread_sidebar_width=256; measured Settings sidebar is 256×1000. The oldsidebarWidthfield in the state files selects the first HTML nav (breadcrumb), so it is not the Settings-sidebar measurement.static-v2-*-layout.jsonsupplements this. Picker geometry in all captures: CSS x464/y293.5/w512/h413. DPR2, CSS/root zoom1, visualViewport.scale1, root font16px, system font stack. Stock light theme is retained byt3code:theme-appearance-mode=light; the supportedenvironmentIdentificationMode=noneremoves environment artwork. The notification dismissal key is retained. No clock freezing or app styling override.static-v2-before-seed.sqliteandstatic-v2-after-seed.sqliteare additional consistent read-only SQLite backups of the isolated running states after UI seeding. They stay local, are not committed/uploaded, and allow inspecting the exact resulting project state. The independent rebuild used the originalseed.sqliteplus the UI recipe, not either post-seed snapshot. Auth state is not part of the replay contract; mint a fresh pairing token. No live~/.t3database was read or changed by this task.
Independent AFTER result
A fresh checkout /tmp/issue-22794-static-after-rebuild at the same current head was installed independently, seeded from the original retained seed, launched on its own ports, paired in a new own tab and replayed once. No visual tweaks followed the measurement.
Independent AFTER capture · Full-surface rebuild diff0 ×8 · Metrics · Changing regions · Recompute diffs.
3,204/1,137,920 pixels differ, all with max-channel delta exactly 1; zero exceed 16. The picker rectangle has zero changed pixels at threshold zero. Whole-surface nonzero bbox is PNG (56,10)–(1235,823). All residuals are outside the picker, in small repeated background patches around x56–99, x1038–1081 and x1192–1235, at y10–36, y752–778 and y798–823. The linked region JSON lists connected components (larger components are 287 pixels each). Geometry, text, storage, theme and scroll match; the source of the one-level rendering residual has not been established. Do not describe it as a proven application change or claim exact whole-surface equality. No repeated tiny adjustments were attempted.
Retained running inventory
- Original AFTER
/home/ubuntu/wt/issue-22794: web7192 PID134942; backend15232 PID242827; inherited, not owned here. New own tabtab_o_f0288758-9d52-4b49-93c6-2f87f67b0762, current-head neutral scoped project, picker ABC open and blurred. - New genuine baseline
/tmp/issue-22794-static-before: dev-runner PID253943; web7529 PID254139; backend15569 PID254158. Owned terminal session63755. Own tabtab_p_f0288758-9d52-4b49-93c6-2f87f67b0762; picker ABC open and blurred. Project scope4eb177c0-3eb5-4045-ac34-5e30796274bd:/tmp/t3-project-icon-proof. - Independent current-head rebuild
/tmp/issue-22794-static-after-rebuild: dev-runner PID259167; web7304 PID259402; backend15344 PID259422. Owned terminal session3563. Own tabtab_q_f0288758-9d52-4b49-93c6-2f87f67b0762; picker ABC open and blurred. Project scopea7fd8cfe-a3d9-42d2-8db3-9e37e19e014e:/tmp/t3-project-icon-proof. - Original AFTER project scope
b5929cbc-f18a-472a-ba50-f3e5425d8261:/tmp/t3-project-icon-proof. Every route is/settings/projects?project=plus URL-encoded scope. Ports, PIDs and/proc/<pid>/cwdchecked. All servers and tabs retained for auditor reconstruction; no process killed. Existing stopped before worktrees untouched.
FRICTION: tool — preview snapshots reduce 1440×1000/DPR2 to 1280×889; no manual resizing. infra — copied seed emits the existing migration divergence warning for ids68–70; apps start and fixture saves succeed. reproduction — independent full-surface result retains 3,204 delta1 background pixels; exact zero-delta requirement remains unmet. No CSS/overlay/caret-phase chasing or app changes.
Final independent proof audit
VERDICT: SUFFICIENT. A fresh auditor replayed the retained static-v2 browser recipe and captured the settled page: 0 / 1,137,920 changed pixels against the delivered AFTER. Both replacement whole-surface diff images recomputed exactly. The two missing final reopens are corrected in the linked recordings. The earlier 3,204 one-level background residual is consistent with rendering rounding; no persistent non-AA mismatch was found. Windows/Electron remains unverified.
Implementation: Claude Opus 5.5 through T3 Code / Claude Agent harness. Triage, verification and proof: GPT-6.1 Sol through T3 Code / Codex harness.
Checks
Added 3 commits
Approvability
Verdict: Approved at 83c7355
Macroscope's review found this PR approvable — This is a localized web bug fix that aligns the existing monogram picker with the server’s two-grapheme limit, preventing invalid saves while preserving valid and legacy behavior. The added JSON is a non-production fixture and introduces no product-default or static-analysis changes.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
- Configuration used: Path: .coderabbit.config.ts
- Review profile: CHILL
- Plan: Team
- Run ID:
09020778-e193-4e72-a8aa-975dd8643849
📥 Commits
Reviewing files that changed from the base of the PR and between 89fe509232e787b51d0cf695861afe73316ce85b and 83c7355eec0708ba5bdc8658d974bc4c4e2a0beb.
📒 Files selected for processing: 2
apps/web/src/components/settings/ProjectIconPickerDialog.tsxscripts/fixtures/project-icon-monograms.json
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
📝 Walkthrough
The project icon picker now requires monograms to pass the existing schema check and contain no more than two grapheme clusters. A fixture adds a project icon preview and monogram cases.
Changes: Monogram validation
Layer / File(s) Summary Picker validation and fixture cases
apps/web/src/components/settings/ProjectIconPickerDialog.tsx,scripts/fixtures/project-icon-monograms.jsonThe picker counts grapheme clusters and requires no more than two, in addition to the existing schema check. The fixture adds preview steps and cases for monogram input, normalization, and validity. Priority: ⬇️ Low
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix · Severity of issue fixed: Low
Possibly related issues
pingdotgg/t3code#22793: The issue describes the picker accepting monograms that the server rejects. This change adds a picker-side grapheme-count check before saving.
Suggested reviewers:
eimexdev
Merge Risk: ⚪ Minimal · up to 83c73
Monograms exceeding two graphemes are rejected before saving, while valid normalized monograms remain saveable. No concrete merge-blocking risk is established; the change appears ready for normal checks.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title is concise, specific, and accurately describes the main fix: preventing project monograms longer than two characters from being saved. Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request. Description check Passed The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, implementation, issue reference and approval rationale, focused… Full details: Description check
Explanation
The pull request description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the bug, implementation, issue reference and approval rationale, focused test results, manual UI verification, screenshots and recordings, unverified environments, and the agent model and harness.
✨ Finishing Touches
📝 Generate docstrings
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.