Repository navigation
feat(app): retire the layout toggle and the theme picker - #181
Merged
Merged
Conversation
Settings -> General loses the "New layout" switch and Settings -> Appearance loses the "Theme" picker. The fork ships one layout and one theme; light/dark stays as the Color scheme axis. Neither is merely hidden. `newLayoutDesigns` is now a constant rather than a read of the persisted preference, so a profile carrying `newLayoutDesigns: false` still comes up on the new layout, and `setNewLayoutDesigns` / `layoutTransitionAvailable` / `newInterfaceNoticeVisible` / `dismissNewInterfaceNotice` are removed from the context rather than stubbed, so re-introducing the toggle is a compile error. The sunset machinery goes with them. For themes the lock is `normalize()` in the theme context, which every entry point already passes through — init, the mount-time migration, `load`, the cross-tab storage listener, `setTheme`, `previewTheme`. That also migrates for free: the existing onMount block rewrites a stale `opencode-theme-id` and clears the cached palette CSS. `oc-theme-preload.js` does the same before mount so an upgrading user never sees a frame of their old palette. The `theme.cycle` command (mod+shift+t) and the per-theme `theme.set.<id>` entries are gone; the colour-scheme commands stay. The bundled theme files, the `names` map and the LegacyServerLayout branches in app.tsx are deliberately left alone — that shell still mounts on /:dir routes, and removing either is a large conflict surface against upstream for no user-visible gain. `legacy-new-session.spec.ts` is deleted (it asserted a redirect to a route that no longer resolves) and `session-timeline-projection` now asserts inline comments instead of the CommentStrip row, which only the retired layout produced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four e2e cases in a new layout-theme-lock spec, seeded with the stored state that used to select the retired layout and themes, plus unit coverage of the two chokepoints. Every case was checked to fail when the matching source file is reverted to upstream's, so none of them passes vacuously: - old-layout profile boots the shipped layout -> context/settings.tsx - stale theme migrated before first paint -> oc-theme-preload.js - a theme another tab writes is ignored -> ui theme context - settings has no switch and no theme picker -> both settings screens Two of those needed more than the obvious assertion. The settings case seeds `layoutTransitionEligible: true`, because upstream only showed the switch to eligible profiles and without it the assertion passes on upstream's code too. The migration case watches for the cached-palette <style> with a MutationObserver installed at document_start: the app removes that element on its way past, so a post-boot query cannot see the frame it would have been on screen for. The observer is anchored on `document` rather than `document.documentElement`, which does not exist yet that early. `normalize()` in the theme context becomes the exported `normalizeThemeID()` returning the exported `SHIPPED_THEME_ID`, so the single-theme invariant is covered by a test rather than only by its six call sites. The module imports fine under `bun test` because `import.meta.glob` is called lazily inside `getFiles()`. theme-preload gains a fresh-profile case, a system-scheme case and an explicit "never injects cached palette css" case — the pre-mount timing the e2e observer watches for, asserted directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
pushed a commit
that referenced
this pull request
Aug 11, 2026
#173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
added a commit
that referenced
this pull request
Aug 11, 2026
* test(app): cover the sidebar, titlebar and session-row menu PRs #173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(app): drop the workarounds #185 makes unnecessary Both specs carried a detour around a defect, each with a comment explaining why. #185 fixes both, so the detours are now dead weight and the comments are wrong. - session-actions-menu: openRowMenu clicked a second time when the first was swallowed. One click now, and the helper no longer needs the page. - sidebar-persistence: "marks the row whose session is already open" reloaded before asserting data-selected. It asserts on the click that opened the session. The hover in openRowMenu stays. That one is not a workaround: the trigger is pointer-events-none until its row is hovered, and Playwright hit-tests before it moves the mouse. Checked both ways rather than assumed. On #185's branch the three specs pass 20/20; on main without it, both edited tests fail — the menu never reports aria-expanded=true, and no row is ever marked selected. So this must merge after #185. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Quickbeard <quickbeard@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Settings → General loses the New layout switch and Settings → Appearance loses the Theme picker. The fork ships one layout and one theme; light/dark stays as the Color scheme axis, and the terminal client keeps its own separate theme picker.
Neither is merely hidden
Layout. The switch came with a whole timed-migration mechanism — a sunset date, a per-profile eligibility flag, a stored
newLayoutDesignspreference. Hiding just the switch would have left a storedfalse(older build, another machine, hand-edited store) still opening the old layout, so the lock is at the source:newLayoutDesignsis now a constanttruethat never reads the persisted value.setNewLayoutDesigns,layoutTransitionAvailable,newInterfaceNoticeVisibleanddismissNewInterfaceNoticeare removed from the context rather than stubbed — a no-op setter would let a caller look like it was switching layouts, so re-introducing the toggle is a compile error. The sunset timer, the retirement notice and the effects that wrote the preference back are deleted;interface-transition.tsxgoes with its last caller.Theme. The lock is
normalize()inui/src/theme/context.tsx, which now returns"oc-2"unconditionally. That one function is already on every path — provider init, the mount-time migration,load, the cross-tabstoragelistener,setTheme,previewTheme— so it needs no second guard, and it gives the migration for free: the existingonMountblock already rewritesopencode-theme-idand clears the cached palette CSS when the stored id differs.oc-theme-preload.jsdoes the same rewrite before mount, so an upgrading user never gets a frame of their old palette. Removed surfaces: the Theme row in both Settings screens (with its…/themesdocs link), thetheme.cyclecommand and its⌘⇧Tkeybind, and thetheme.set.<id>entries. The colour-scheme commands stay —⌘⇧Sand the three Use color scheme: … entries still work.Deliberately left alone
The 36 bundled theme JSON files, the
namesmap, and theLegacyServerLayoutbranches inapp.tsx. That shell still mounts on/:dirroutes, and removing either is a large conflict surface against upstream for no user-visible gain.Tests
legacy-new-session.spec.tsis deleted — it asserted a redirect to a route that no longer resolves.session-timeline-projectionneeded real work rather than a mechanical fix: it asserted aCommentStriptimeline row, which turns out to be legacy-only (inlineCommentsis driven by the layout flag, and the new layout renders comments inline on the user message). Rewritten to assert the inline rendering plus the diff-summary overflow, keeping the coverage.Verification
Drove the built desktop app over CDP: no New layout section; Appearance reads Color scheme → UI Font → Code Font → Terminal Font with no Theme row;
data-themeisoc-2, the stored theme id was rewritten and the cached CSS cleared; the servedlayout.tsxmodule contains onlytheme.scheme.*commands.remote-session-settings.spec.tsand reproduce on a cleanmain, so they are pre-existing and unrelatedelectron-builder.config.ts)DIVERGENCES.mdgains two rows with conflict-resolution guidance, andCHANGELOG.mdan Unreleased entry.🤖 Generated with Claude Code