Skip to content

feat(console): new ui improvements - #733

Merged
sergiofilhowz merged 3 commits into
mainfrom
feat/console-new-uiimprovements
Aug 7, 2026
Merged

sergiofilhowz merged 3 commits into
mainfrom
feat/console-new-uiimprovements

Conversation

@sergiofilhowz

@sergiofilhowz sergiofilhowz commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Improvements on the UI

  • updated state to be consistent with new console ui
  • updated font to be cleaner
  • reduced the time to create new panels
  • improved memory ui to be consistent with new console
image

Summary by CodeRabbit

  • New Features
    • Added responsive narrow-pane drill-in navigation across Memory, State, Browser, Computer, Database, GitHub, and Worktree pages.
    • Added State browsing and JSON editing with validation, saving, live updates, conflict notices, and unsaved-change protection.
    • Shell Explorer now follows the active conversation’s working directory.
  • UI Improvements
    • Introduced redesigned layouts, loading and empty states, segmented controls, status indicators, refresh actions, and accessible navigation.
    • Updated the console interface typography and visual styling with improved focus and reduced-motion support.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview Aug 7, 2026 11:27am
workers-tech-spec Ready Ready Preview Aug 7, 2026 11:27am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sergiofilhowz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 223b882d-a0e6-4b5b-a5a2-3750a1017ba7

📥 Commits

Reviewing files that changed from the base of the PR and between 6e04293 and 3d9ee24.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • console/web/package.json
  • console/web/src/App.tsx
  • console/web/src/components/chat/ChatPanel.tsx
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/MessageList.tsx
  • console/web/src/components/sidebar/ConversationSidebar.tsx
  • console/web/src/hooks/use-container-narrow.ts
  • console/web/src/hooks/use-hash-route.test.ts
  • console/web/src/hooks/use-hash-route.ts
  • console/web/src/pages/Configuration/tabs/WorkersTab/EmptyState.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/SaveBar.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/WorkerEditor.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/WorkersList.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/WorkersTab.stories.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/index.tsx
  • console/web/src/pages/Workers/components/WorkerConfigurationPanel.tsx
  • console/web/src/pages/Workers/index.tsx
📝 Walkthrough

Walkthrough

Changes

This PR redesigns multiple console extension pages around shared PageShell and PageHeader components. It adds responsive narrow-pane drill-in navigation, page render-prop forwarding, dirty-state guards, redesigned loading and empty states, scoped styling, and working-directory propagation.

Console extension workspaces

Layer / File(s) Summary
Memory workspace
memory/ui/...
Adds responsive bank navigation, persisted panel selection, dirty-state tracking for memory and rules editors, keyboard saves, and namespaced styles.
State manager workspace
state/ui/...
Replaces the previous route views with StateBrowser and ValueEditor, including live updates, JSON validation, save handling, conflict notices, and narrow navigation.
Browser and computer workspaces
browser/ui/..., computer/ui/...
Adds responsive session rails, workspace drill-in navigation, live status controls, conditional streaming, and redesigned viewport/feed layouts.
Database, GitHub, and worktree workspaces
database/ui/..., github/ui/..., worktree/ui/...
Migrates pages to shared chrome and responsive layouts with panel persistence, dirty-state confirmation, graph/detail drill-in, and scoped styles.
Shared page wiring
packages/console-ui/..., console/web/..., shell/ui/..., */ui/page.tsx
Forwards page render props, propagates the active conversation working directory, updates shell root selection, and changes the console sans font to Inter.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: no-ticket

Suggested reviewers: rohitg00

Poem

I’m a rabbit in a pane,
Hopping through the new shell lane.
Narrow paths and editors bright,
Dirty drafts guarded just right.
Inter fonts and graphs now gleam—
Fresh UI hops through every stream.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the console UI changes but is too broad and does not identify the main redesign, responsive navigation, or state consistency work. Use a specific title that summarizes the primary console UI redesign, such as responsive page layouts and shared navigation updates.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/console-new-uiimprovements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
memory/ui/src/page/BankRail.tsx (2)

56-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the empty-state copy with the page voice.

The rest of the page uses lowercase prose. memory/ui/src/page/index.tsx lines 281-285 render "no banks yet" and "create one on the left, ...". This block uses "No banks yet." and "Create one below, ...". Use one style for both empty states.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memory/ui/src/page/BankRail.tsx` around lines 56 - 63, Update the empty-state
copy in the BankRail component to match the lowercase prose used by the
corresponding empty state in index.tsx: change the sentence starts in “No banks
yet.” and “Create one below...” to lowercase while preserving the existing
wording and layout.

36-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Announce the loading skeleton as busy.

The skeleton classes and width helpers are defined, but the aria-hidden skeleton hides the loading state from screen readers until initialLoad ends. Set aria-busy={initialLoad} on the rail container while loading.

♿ Proposed change
-    <aside className="mem-ui-rail" aria-label="bank list">
+    <aside className="mem-ui-rail" aria-label="bank list" aria-busy={initialLoad}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memory/ui/src/page/BankRail.tsx` around lines 36 - 55, Set
aria-busy={initialLoad} on the bank rail container aside in BankRail so the
loading skeleton state is announced to assistive technologies while preserving
the existing skeleton rendering.
memory/ui/src/page/index.tsx (1)

70-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the shared page helpers into one module.

useContainerNarrow here is byte-identical to useContainerNarrow in state/ui/src/page/browser.tsx (lines 95-115), and confirmDiscard repeats the implementation at state/ui/src/page/browser.tsx lines 162-163. readStored/writeStored also duplicate the best-effort localStorage pattern used in memory/ui/src/page/memory-data.ts. A shared module (or an export from @iii-dev/console-ui) keeps the narrow threshold semantics and the guard prompt text in sync across pages.

This is optional for this PR. The current code is correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memory/ui/src/page/index.tsx` around lines 70 - 115, Optionally extract the
duplicated page helpers into a shared module, reusing the existing
useContainerNarrow implementation, confirmDiscard behavior and prompt text, and
readStored/writeStored localStorage guards. Update memory/ui/src/page/index.tsx
and the corresponding browser or memory-data callers to import the shared
symbols while preserving the current threshold semantics and best-effort
persistence behavior.
state/ui/src/page/index.tsx (1)

17-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use PanelSide in StateBrowser.

PanelSide is exported from @iii-dev/console-ui as 'left' | 'right', and PageRenderProps['panelSide'] uses that alias. Import PanelSide in state/ui/src/page/browser.tsx and use it for the optional prop so the child type stays aligned with the page render prop contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@state/ui/src/page/index.tsx` around lines 17 - 31, Update StateBrowser’s
optional panelSide prop in the StateBrowser component to use the exported
PanelSide type from `@iii-dev/console-ui`. Import PanelSide in browser.tsx and
replace the narrower or duplicated prop type, preserving the existing left/right
behavior and alignment with PageRenderProps['panelSide'].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memory/ui/styles.css`:
- Line 403: Replace the deprecated word-break: break-word declaration with
overflow-wrap: anywhere in memory/ui/styles.css lines 403-403 (.mem-ui-banner
.detail), 447-447 (.mem-ui-error-panel p), and 820-820 (.mem-ui-prompt-body),
plus state/ui/styles.css lines 291-291 (.state-ui-error-panel p) and 403-403
(.state-ui-banner .detail).

In `@packages/console-ui/index.d.ts`:
- Around line 76-82: Synchronize the declared workingDir prop with the runtime
rendering path: update ScreenBody and ExtPage in
console/web/src/pages/Ext/index.tsx to accept and forward workingDir alongside
panelSide, tabId, and onRequestClose, sourcing the active conversation’s current
directory; alternatively remove workingDir from the console-ui declaration if
the runtime cannot provide it.

In `@shell/ui/src/page/index.tsx`:
- Line 109: The explorer trusts workingDir without validating it against allowed
base paths. In shell/ui/src/page/index.tsx:109-109, add a shared
withinBase-based validation helper or normalized chat value and use it in the
persisted ?? workingDir ?? info.primary_root fallback; in
shell/ui/src/page/index.tsx:239-239, apply the same validation before
changeRoot(next) so invalid chat folders cannot re-root the explorer.

In `@state/ui/src/page/ValueEditor.tsx`:
- Around line 88-93: Update the conflict check in the ValueEditor live-update
handler to use the current dirty state rather than only checking whether
draftRef.current is non-null. Store the derived dirty value in dirtyRef.current
alongside the existing dirty derivation, then require that state when deciding
whether to show serverNotice and block applying nextText.

---

Nitpick comments:
In `@memory/ui/src/page/BankRail.tsx`:
- Around line 56-63: Update the empty-state copy in the BankRail component to
match the lowercase prose used by the corresponding empty state in index.tsx:
change the sentence starts in “No banks yet.” and “Create one below...” to
lowercase while preserving the existing wording and layout.
- Around line 36-55: Set aria-busy={initialLoad} on the bank rail container
aside in BankRail so the loading skeleton state is announced to assistive
technologies while preserving the existing skeleton rendering.

In `@memory/ui/src/page/index.tsx`:
- Around line 70-115: Optionally extract the duplicated page helpers into a
shared module, reusing the existing useContainerNarrow implementation,
confirmDiscard behavior and prompt text, and readStored/writeStored localStorage
guards. Update memory/ui/src/page/index.tsx and the corresponding browser or
memory-data callers to import the shared symbols while preserving the current
threshold semantics and best-effort persistence behavior.

In `@state/ui/src/page/index.tsx`:
- Around line 17-31: Update StateBrowser’s optional panelSide prop in the
StateBrowser component to use the exported PanelSide type from
`@iii-dev/console-ui`. Import PanelSide in browser.tsx and replace the narrower or
duplicated prop type, preserving the existing left/right behavior and alignment
with PageRenderProps['panelSide'].
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6466c61b-2390-48ed-be91-f85c54c5e279

📥 Commits

Reviewing files that changed from the base of the PR and between 8c0c1ae and 13d0f83.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • memory/ui/page.tsx
  • memory/ui/src/page/BankRail.tsx
  • memory/ui/src/page/MemoriesPanel.tsx
  • memory/ui/src/page/MemoryGraph.tsx
  • memory/ui/src/page/ModeToggle.tsx
  • memory/ui/src/page/RecallPanel.tsx
  • memory/ui/src/page/RulesPanel.tsx
  • memory/ui/src/page/icons.tsx
  • memory/ui/src/page/index.tsx
  • memory/ui/src/page/widgets.tsx
  • memory/ui/styles.css
  • packages/console-ui/index.d.ts
  • shell/ui/page.tsx
  • shell/ui/src/page/index.tsx
  • state/ui/page.tsx
  • state/ui/src/lib/widgets.tsx
  • state/ui/src/page/ItemView.tsx
  • state/ui/src/page/ItemsView.tsx
  • state/ui/src/page/ScopesView.tsx
  • state/ui/src/page/ValueEditor.tsx
  • state/ui/src/page/browser.tsx
  • state/ui/src/page/index.tsx
  • state/ui/styles.css
💤 Files with no reviewable changes (3)
  • state/ui/src/page/ScopesView.tsx
  • state/ui/src/page/ItemView.tsx
  • state/ui/src/page/ItemsView.tsx

Comment thread memory/ui/styles.css
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 11.5px;
color: var(--color-ink-faint);
word-break: break-word;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Deprecated word-break: break-word in both redesigned stylesheets. Stylelint 17.14.0 reports declaration-property-value-keyword-no-deprecated at all five declarations. The shared root cause is the break-word keyword on word-break; the supported replacement is overflow-wrap: anywhere.

  • memory/ui/styles.css#L403-L403: replace word-break: break-word with overflow-wrap: anywhere in .mem-ui-banner .detail.
  • memory/ui/styles.css#L447-L447: replace word-break: break-word with overflow-wrap: anywhere in .mem-ui-error-panel p.
  • memory/ui/styles.css#L820-L820: replace word-break: break-word with overflow-wrap: anywhere in .mem-ui-prompt-body.
  • state/ui/styles.css#L291-L291: replace word-break: break-word with overflow-wrap: anywhere in .state-ui-error-panel p.
  • state/ui/styles.css#L403-L403: replace word-break: break-word with overflow-wrap: anywhere in .state-ui-banner .detail.
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 403-403: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

📍 Affects 2 files
  • memory/ui/styles.css#L403-L403 (this comment)
  • memory/ui/styles.css#L447-L447
  • memory/ui/styles.css#L820-L820
  • state/ui/styles.css#L291-L291
  • state/ui/styles.css#L403-L403
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memory/ui/styles.css` at line 403, Replace the deprecated word-break:
break-word declaration with overflow-wrap: anywhere in memory/ui/styles.css
lines 403-403 (.mem-ui-banner .detail), 447-447 (.mem-ui-error-panel p), and
820-820 (.mem-ui-prompt-body), plus state/ui/styles.css lines 291-291
(.state-ui-error-panel p) and 403-403 (.state-ui-banner .detail).

Source: Linters/SAST tools

Comment thread packages/console-ui/index.d.ts
const withinBase = (p: string) =>
info.base_paths.includes(p) || info.base_paths.some((base) => p.startsWith(`${base}/`))
const persisted = restored?.root && withinBase(restored.root) ? restored.root : null
const next = persisted ?? workingDir ?? info.primary_root

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

workingDir is used as a root without base-path validation. The component validates a persisted root with withinBase but trusts workingDir in both root paths. If the chat working directory lies outside info.base_paths, the explorer roots on a path the worker refuses, and refreshTree masks the failure with an empty tree.

  • shell/ui/src/page/index.tsx#L109-L109: apply withinBase(workingDir) before using it in the fallback chain, for example const chat = workingDir && withinBase(workingDir) ? workingDir : null and then persisted ?? chat ?? info.primary_root.
  • shell/ui/src/page/index.tsx#L239-L239: lift the same base-path check into a shared helper and call it before changeRoot(next), so a chat folder outside the allowed bases does not re-root the explorer.
📍 Affects 1 file
  • shell/ui/src/page/index.tsx#L109-L109 (this comment)
  • shell/ui/src/page/index.tsx#L239-L239
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shell/ui/src/page/index.tsx` at line 109, The explorer trusts workingDir
without validating it against allowed base paths. In
shell/ui/src/page/index.tsx:109-109, add a shared withinBase-based validation
helper or normalized chat value and use it in the persisted ?? workingDir ??
info.primary_root fallback; in shell/ui/src/page/index.tsx:239-239, apply the
same validation before changeRoot(next) so invalid chat folders cannot re-root
the explorer.

Comment on lines +88 to +93
if (draftRef.current !== null && draftRef.current !== nextText) {
// Never clobber an editor with unsaved changes — offer the new
// value instead.
setServerNotice({ kind: 'changed', value: e.new_value })
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the dirty state, not draft !== null, to detect a conflict.

draftRef.current stays non-null after the user edits and then types the value back to the original text. In that case dirty is false, but this branch still shows the "changed on the server while you were editing" banner and skips the in-place apply. The editor then shows a stale value while the status bar reports "all changes saved".

Compare the draft against the stored text so only a truly dirty editor blocks the live update.

🐛 Proposed fix
-  const draftRef = useRef(draft)
-  draftRef.current = draft
+  const draftRef = useRef(draft)
+  draftRef.current = draft
+  const dirtyRef = useRef(false)
     const nextText = JSON.stringify(e.new_value ?? null, null, 2)
-    if (draftRef.current !== null && draftRef.current !== nextText) {
+    if (dirtyRef.current && draftRef.current !== nextText) {

Set dirtyRef.current = dirty next to the existing dirty derivation (line 71).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@state/ui/src/page/ValueEditor.tsx` around lines 88 - 93, Update the conflict
check in the ValueEditor live-update handler to use the current dirty state
rather than only checking whether draftRef.current is non-null. Store the
derived dirty value in dirtyRef.current alongside the existing dirty derivation,
then require that state when deciding whether to show serverNotice and block
applying nextText.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 55 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (7)
computer/ui/src/page/index.tsx (1)

45-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the duplicate narrow layout hook to a shared package if the threshold is stable.

computer/ui/src/page/index.tsx repeats the hook found in browser/ui/src/page/index.tsx and github/ui/src/page/narrow.ts. The three implementations share the same threshold where they are duplicated, but other pages define different NARROW_BELOW values, so a shared module should name/threshold the shared behavior instead of duplicating it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@computer/ui/src/page/index.tsx` around lines 45 - 75, Extract the duplicated
useContainerNarrow behavior into a shared package, parameterized by the caller’s
NARROW_BELOW threshold, and update the implementations in
computer/ui/src/page/index.tsx, browser/ui/src/page/index.tsx, and
github/ui/src/page/narrow.ts to reuse it. Preserve each page’s existing
threshold and callback-ref behavior, while leaving pages with different
NARROW_BELOW values configurable.
worktree/ui/src/page/index.tsx (1)

39-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider importing the shared narrow helper instead of copying it.

useContainerNarrow is byte-identical to the copies in browser/ui/src/page/index.tsx, database/ui/src/page/index.tsx, github/ui/src/page/index.tsx, and github/ui/src/page/GitGraph.tsx. github/ui/src/page/narrow.ts already exports this helper. Move the hook to one shared module and import it in each page. Keep NARROW_BELOW per page, because the thresholds differ by layout.

If each extension must bundle independently, then this duplication is acceptable and you can skip this change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worktree/ui/src/page/index.tsx` around lines 39 - 70, Replace the duplicated
useContainerNarrow implementation in the page components with imports from the
existing shared narrow helper module. Remove each local hook definition while
preserving the per-page NARROW_BELOW constants and their existing threshold
behavior; if extensions cannot share that module when bundled independently,
leave the duplication unchanged.
worktree/ui/styles.css (1)

116-124: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Scope the skeleton width classes under .wt-ui-skel.

.bar, .w40, .w60, and .w75 are generic names. These rules match any descendant of the worktree wrapper, including markup rendered by @iii-dev/console-ui components. Nest them under .wt-ui-skel as line 100 already does.

♻️ Proposed scoping fix
-[data-iii-ui="worktree"] .bar.w40 {
+[data-iii-ui="worktree"] .wt-ui-skel .bar.w40 {
   width: 40%;
 }
-[data-iii-ui="worktree"] .bar.w60 {
+[data-iii-ui="worktree"] .wt-ui-skel .bar.w60 {
   width: 60%;
 }
-[data-iii-ui="worktree"] .bar.w75 {
+[data-iii-ui="worktree"] .wt-ui-skel .bar.w75 {
   width: 75%;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worktree/ui/styles.css` around lines 116 - 124, Scope the `.bar.w40`,
`.bar.w60`, and `.bar.w75` selectors under the `.wt-ui-skel` container, matching
the existing skeleton rule structure, so they only affect skeleton markup within
the worktree UI.
github/ui/styles.css (1)

145-162: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Scope the skeleton width classes under .gh-ui-skel.

.bar.w30 through .bar.w90 match any element in the github UI that carries a bar class. The sibling rule at line 129 is already scoped to .gh-ui-skel .bar. Use the same scope for the width modifiers so a future bar class elsewhere does not inherit a width.

♻️ Proposed scoping
-[data-iii-ui="github"] .bar.w30 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w30 {
   width: 30%;
 }
-[data-iii-ui="github"] .bar.w40 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w40 {
   width: 40%;
 }
-[data-iii-ui="github"] .bar.w60 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w60 {
   width: 60%;
 }
-[data-iii-ui="github"] .bar.w75 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w75 {
   width: 75%;
 }
-[data-iii-ui="github"] .bar.w85 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w85 {
   width: 85%;
 }
-[data-iii-ui="github"] .bar.w90 {
+[data-iii-ui="github"] .gh-ui-skel .bar.w90 {
   width: 90%;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/ui/styles.css` around lines 145 - 162, Scope the `.bar.w30` through
`.bar.w90` width modifier selectors under `.gh-ui-skel`, matching the existing
`.gh-ui-skel .bar` rule so only skeleton bars receive these widths.
github/ui/src/page/GitGraph.tsx (1)

485-495: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving focus to the back button on narrow drill-in.

In narrow mode the commit row unmounts when the detail opens. Focus falls back to the document body, so the next Tab restarts at the top of the pane. Focus BackButton when the detail mounts in narrow mode to keep the keyboard path continuous.

♻️ Proposed focus handling
-function BackButton({ onClick, label }: { onClick: () => void; label: string }) {
+function BackButton({ onClick, label, autoFocus }: { onClick: () => void; label: string; autoFocus?: boolean }) {
+  const ref = useRef<HTMLButtonElement | null>(null)
+  useEffect(() => {
+    if (autoFocus) ref.current?.focus()
+  }, [autoFocus])
   return (
-    <button type="button" className="gh-ui-back" onClick={onClick} aria-label={label} title={label}>
+    <button ref={ref} type="button" className="gh-ui-back" onClick={onClick} aria-label={label} title={label}>
       <ChevronLeft className="gh-ui-back-icon" />
     </button>
   )
 }
-        {narrow ? <BackButton onClick={onClose} label="back to the graph" /> : null}
+        {narrow ? <BackButton onClick={onClose} label="back to the graph" autoFocus /> : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/ui/src/page/GitGraph.tsx` around lines 485 - 495, Update the narrow
commit-detail mount flow in GitGraph so focus moves to the rendered BackButton
after the commit row unmounts. Use a ref or the component’s existing focus
mechanism, apply it only when narrow is true, and preserve the current onClose
behavior and wide-mode close button.
database/ui/src/page/index.tsx (1)

71-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider sharing useContainerNarrow instead of copying it.

useContainerNarrow is now duplicated verbatim in database/ui/src/page/index.tsx, browser/ui/src/page/index.tsx, computer/ui/src/page/index.tsx, github/ui/src/page/narrow.ts, and worktree pages. github/ui/src/page/narrow.ts already exports it next to NARROW_BELOW = 720. Each extension can keep its own threshold constant while importing one hook implementation. If the extensions must build as independent bundles, then keep the copies and ignore this note.

Also applies to: 92-118

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/src/page/index.tsx` around lines 71 - 73, Replace the duplicated
useContainerNarrow implementation in the page module with the shared hook
exported from github/ui/src/page/narrow.ts, while retaining the local
NARROW_BELOW threshold where each extension requires a different value. If
independent bundle constraints prevent importing the shared hook, leave the
local implementation unchanged.
database/ui/src/page/SqlPanel.tsx (1)

152-168: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the dirty callback ref update out of the render body.

This project uses React 19, where render-phase ref updates are discouraged due to concurrent rendering. The current assignment works here because the ref is read inside effects, but use a layout effect for the update if this value is only used to stabilize callbacks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/src/page/SqlPanel.tsx` around lines 152 - 168, Move the
onDirtyChangeRef.current assignment out of the render body and into a layout
effect, using the appropriate React layout-effect hook and onDirtyChange
dependency. Keep the existing dirty-state and unmount cleanup effects unchanged,
so they continue invoking the latest callback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser/ui/styles.css`:
- Around line 79-88: Replace every deprecated word-break: break-word declaration
in styles.css, including the banner selectors and the occurrences near the other
flagged locations, with overflow-wrap: anywhere to preserve the current wrapping
behavior and satisfy Stylelint.

In `@computer/ui/src/page/index.tsx`:
- Around line 215-229: Update the banner button in the problem-rendering branch
of the page component to display “retry” when it invokes refresh(), matching the
equivalent control in the referenced page implementation; preserve its existing
click behavior and error-clearing logic.

In `@computer/ui/styles.css`:
- Around line 74-76: Update the [data-iii-ui="computer"] .cp-ui-banner > span
rule by replacing the deprecated word-break declaration with overflow-wrap:
anywhere, preserving wrapping behavior for long banner strings.

In `@console/web/src/App.tsx`:
- Line 380: Update the workingDir effect in App to reuse ShellExplorerPage’s
dirty-state confirmation before invoking changeRoot, preventing conversation or
folder changes from discarding unsaved edits; preserve the existing root-change
behavior when clean and add coverage for a dirty editor followed by a workingDir
change.

In `@database/ui/src/page/index.tsx`:
- Around line 516-522: Update the narrow-mode back navigation around BackButton
so drilling out moves focus to the table-list aside after setDrilled(false)
hides db-ui-main. Add the appropriate ref and keyboard focus target to the
aside, and route the BackButton through a drillOut handler that clears drilled
state and focuses the table list.

In `@database/ui/styles.css`:
- Around line 492-507: Move the dark-theme derived-ink overrides for the
database selectors, including `.db-sql-warn`, after the corresponding
light-theme rules so their matching-specificity declarations take effect in dark
mode. Preserve the existing colors and selector scope, changing only the rule
ordering or adjacency as needed.

---

Nitpick comments:
In `@computer/ui/src/page/index.tsx`:
- Around line 45-75: Extract the duplicated useContainerNarrow behavior into a
shared package, parameterized by the caller’s NARROW_BELOW threshold, and update
the implementations in computer/ui/src/page/index.tsx,
browser/ui/src/page/index.tsx, and github/ui/src/page/narrow.ts to reuse it.
Preserve each page’s existing threshold and callback-ref behavior, while leaving
pages with different NARROW_BELOW values configurable.

In `@database/ui/src/page/index.tsx`:
- Around line 71-73: Replace the duplicated useContainerNarrow implementation in
the page module with the shared hook exported from github/ui/src/page/narrow.ts,
while retaining the local NARROW_BELOW threshold where each extension requires a
different value. If independent bundle constraints prevent importing the shared
hook, leave the local implementation unchanged.

In `@database/ui/src/page/SqlPanel.tsx`:
- Around line 152-168: Move the onDirtyChangeRef.current assignment out of the
render body and into a layout effect, using the appropriate React layout-effect
hook and onDirtyChange dependency. Keep the existing dirty-state and unmount
cleanup effects unchanged, so they continue invoking the latest callback.

In `@github/ui/src/page/GitGraph.tsx`:
- Around line 485-495: Update the narrow commit-detail mount flow in GitGraph so
focus moves to the rendered BackButton after the commit row unmounts. Use a ref
or the component’s existing focus mechanism, apply it only when narrow is true,
and preserve the current onClose behavior and wide-mode close button.

In `@github/ui/styles.css`:
- Around line 145-162: Scope the `.bar.w30` through `.bar.w90` width modifier
selectors under `.gh-ui-skel`, matching the existing `.gh-ui-skel .bar` rule so
only skeleton bars receive these widths.

In `@worktree/ui/src/page/index.tsx`:
- Around line 39-70: Replace the duplicated useContainerNarrow implementation in
the page components with imports from the existing shared narrow helper module.
Remove each local hook definition while preserving the per-page NARROW_BELOW
constants and their existing threshold behavior; if extensions cannot share that
module when bundled independently, leave the duplication unchanged.

In `@worktree/ui/styles.css`:
- Around line 116-124: Scope the `.bar.w40`, `.bar.w60`, and `.bar.w75`
selectors under the `.wt-ui-skel` container, matching the existing skeleton rule
structure, so they only affect skeleton markup within the worktree UI.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a91d492-509f-406b-a4da-cc4148145cda

📥 Commits

Reviewing files that changed from the base of the PR and between 13d0f83 and 6e04293.

📒 Files selected for processing (38)
  • browser/ui/page.tsx
  • browser/ui/src/lib/widgets.tsx
  • browser/ui/src/page/SessionRail.tsx
  • browser/ui/src/page/SessionView.tsx
  • browser/ui/src/page/Viewport.tsx
  • browser/ui/src/page/index.tsx
  • browser/ui/styles.css
  • computer/ui/page.tsx
  • computer/ui/src/lib/widgets.tsx
  • computer/ui/src/page/SessionRail.tsx
  • computer/ui/src/page/index.tsx
  • computer/ui/styles.css
  • console/web/package.json
  • console/web/src/App.tsx
  • console/web/src/components/chat/LexicalShell.tsx
  • console/web/src/components/workspace/pane-controls.tsx
  • console/web/src/index.css
  • console/web/src/pages/Ext/index.tsx
  • console/web/src/types/injectable-ui.ts
  • database/ui/page.tsx
  • database/ui/src/page/SqlPanel.tsx
  • database/ui/src/page/index.tsx
  • database/ui/styles.css
  • github/ui/page.tsx
  • github/ui/src/page/GitGraph.tsx
  • github/ui/src/page/GithubPage.tsx
  • github/ui/src/page/icons.tsx
  • github/ui/src/page/index.tsx
  • github/ui/src/page/narrow.ts
  • github/ui/src/page/result-views.tsx
  • github/ui/styles.css
  • worktree/ui/page.tsx
  • worktree/ui/src/page/WorktreeDetailPanel.tsx
  • worktree/ui/src/page/WorktreeGraph.tsx
  • worktree/ui/src/page/icons.tsx
  • worktree/ui/src/page/index.tsx
  • worktree/ui/src/page/worktree-data.ts
  • worktree/ui/styles.css

Comment thread browser/ui/styles.css
Comment on lines +79 to 88
[data-iii-ui="browser"] .br-ui-banner > span {
word-break: break-word;
}
[data-iii-ui="browser"] .br-ui-banner .detail {
display: block;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 11.5px;
color: var(--color-ink-faint);
text-transform: lowercase;
word-break: break-word;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the deprecated word-break: break-word keyword.

Stylelint flags break-word as deprecated for word-break at lines 80, 87, 163, and 792. Use overflow-wrap: anywhere for the same wrapping behavior.

♻️ Proposed change for lines 79-88
 [data-iii-ui="browser"] .br-ui-banner > span {
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
 [data-iii-ui="browser"] .br-ui-banner .detail {
   display: block;
   font-family: var(--font-mono, ui-monospace, monospace);
   font-size: 11.5px;
   color: var(--color-ink-faint);
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }

Apply the same change at lines 163 and 792.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[data-iii-ui="browser"] .br-ui-banner > span {
word-break: break-word;
}
[data-iii-ui="browser"] .br-ui-banner .detail {
display: block;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 11.5px;
color: var(--color-ink-faint);
text-transform: lowercase;
word-break: break-word;
}
[data-iii-ui="browser"] .br-ui-banner > span {
overflow-wrap: anywhere;
}
[data-iii-ui="browser"] .br-ui-banner .detail {
display: block;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 11.5px;
color: var(--color-ink-faint);
overflow-wrap: anywhere;
}
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 80-80: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 87-87: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser/ui/styles.css` around lines 79 - 88, Replace every deprecated
word-break: break-word declaration in styles.css, including the banner selectors
and the occurrences near the other flagged locations, with overflow-wrap:
anywhere to preserve the current wrapping behavior and satisfy Stylelint.

Source: Linters/SAST tools

Comment on lines 215 to 229
{problem ? (
<p className="cp-ui-page-error">
{problem}
<Button
variant="ghost"
size="sm"
<div className="cp-ui-banner alert" role="alert">
<span>{problem}</span>
<button
type="button"
className="cp-ui-linkish"
onClick={() => {
setActionError(null)
refresh()
}}
>
dismiss
</Button>
</p>
</button>
</div>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Label the banner control "retry" when it retries.

problem is actionError ?? error. When the list request failed, actionError is null and the click only calls refresh(). If the refresh fails again, the banner stays visible, so the "dismiss" label does not describe the result. browser/ui/src/page/index.tsx labels the same control "retry".

🔤 Proposed fix for the control label
           <button
             type="button"
             className="cp-ui-linkish"
             onClick={() => {
               setActionError(null)
               refresh()
             }}
           >
-            dismiss
+            retry
           </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{problem ? (
<p className="cp-ui-page-error">
{problem}
<Button
variant="ghost"
size="sm"
<div className="cp-ui-banner alert" role="alert">
<span>{problem}</span>
<button
type="button"
className="cp-ui-linkish"
onClick={() => {
setActionError(null)
refresh()
}}
>
dismiss
</Button>
</p>
</button>
</div>
) : null}
{problem ? (
<div className="cp-ui-banner alert" role="alert">
<span>{problem}</span>
<button
type="button"
className="cp-ui-linkish"
onClick={() => {
setActionError(null)
refresh()
}}
>
retry
</button>
</div>
) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@computer/ui/src/page/index.tsx` around lines 215 - 229, Update the banner
button in the problem-rendering branch of the page component to display “retry”
when it invokes refresh(), matching the equivalent control in the referenced
page implementation; preserve its existing click behavior and error-clearing
logic.

Comment thread computer/ui/styles.css
Comment on lines +74 to +76
[data-iii-ui="computer"] .cp-ui-banner > span {
word-break: break-word;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the deprecated word-break: break-word keyword.

Stylelint reports declaration-property-value-keyword-no-deprecated for this declaration. Use overflow-wrap: anywhere to keep the same wrapping of long banner strings.

🎨 Proposed fix for the deprecated keyword
 [data-iii-ui="computer"] .cp-ui-banner > span {
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[data-iii-ui="computer"] .cp-ui-banner > span {
word-break: break-word;
}
[data-iii-ui="computer"] .cp-ui-banner > span {
overflow-wrap: anywhere;
}
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 75-75: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@computer/ui/styles.css` around lines 74 - 76, Update the
[data-iii-ui="computer"] .cp-ui-banner > span rule by replacing the deprecated
word-break declaration with overflow-wrap: anywhere, preserving wrapping
behavior for long banner strings.

Source: Linters/SAST tools

Comment thread console/web/src/App.tsx
tabId={tabId}
onRequestClose={onClose}
onMissing={onExtMissing}
workingDir={active?.workingDir ?? null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 10 'changeRoot|dirtyPaths|lastWorkingDirRef|workingDir' shell/ui/src/page/index.tsx shell

Repository: iii-hq/workers

Length of output: 22735


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== App.tsx workingDir props =="
fd -a 'App.tsx$' . | sed 's#^\./##' | while read -r f; do
  echo "--- $f"
  rg -n -C 5 'ShellExplorerPage|workingDir|active' "$f" || true
done

echo "== shell/ui source candidates =="
rg -n -C 5 'ShellExplorerPage|changeRoot|dirtyPaths|onSave|onSaved|save|saveAll|unsaved|workingDir' shell/ui/src || true

echo "== tests mentioning shell/unsaved/workingDir =="
rg -n -C 5 'ShellExplorerPage|changeRoot|dirtyPaths|workingDir|unsaved|saveAll|workspace|app' shell ui console 2>/dev/null || true

Repository: iii-hq/workers

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== console/web/src/App.tsx workingDir lines =="
sed -n '350,395p' console\web\src\App.tsx

echo "== ShellExplorerPage usages =="
rg -n -C 6 'ShellExplorerPage\(' shell/ui/src/console/web || true

echo "== shell tests mentioning dirty/unsaved/workingDir/close-tab/changeRoot =="
rg -n -C 8 'Dirty|dirty|unsaved|changeRoot|workingDir|onCloseTab|close' shell/ui src console/web 2>/dev/null | head -n 300 || true

echo "== save/onSaved callback propagation =="
rg -n -C 4 'onSaved|onSave|saveAll|setDirtyPaths|dirtyPaths' shell/ui/src/page/index.tsx console/web/src/App.tsx

Repository: iii-hq/workers

Length of output: 263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate App.tsx and shell/ui paths =="
git ls-files | grep -E '(^|/)App\.tsx$|shell/ui/src/page/index\.tsx$|shell/ui/src/.*test|console/web/src' | head -n 200

echo "== console/web/src/App.tsx workingDir lines =="
sed -n '350,395p' console/web/src/App.tsx || true

echo "== ShellExplorerPage usages =="
rg -n -C 8 'ShellExplorerPage\(' . || true

echo "== shell tests mentioning dirty/unsaved/workingDir/close-tab/changeRoot =="
rg -n -C 8 'Dirty|dirty|unsaved|changeRoot|workingDir|onCloseTab|close' shell ui console 2>/dev/null | head -n 400 || true

echo "== save/onSaved callback propagation =="
rg -n -C 4 'onSaved|onSave|saveAll|setDirtyPaths|dirtyPaths' shell/ui/src/page/index.tsx console/web/src/App.tsx || true

Repository: iii-hq/workers

Length of output: 10963


Guard unsaved shell edits before applying workingDir.

ShellExplorerPage clears dirtyPaths, cache, tabs, and diff when changeRoot runs. The workingDir effect calls changeRoot(next) whenever the conversation root changes, so switching conversations or folders while editing can drop unsaved shell changes without confirmation. Reuse the dirty-state confirmation before calling changeRoot, and add a test for a dirty editor followed by a workingDir change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/App.tsx` at line 380, Update the workingDir effect in App to
reuse ShellExplorerPage’s dirty-state confirmation before invoking changeRoot,
preventing conversation or folder changes from discarding unsaved edits;
preserve the existing root-change behavior when clean and add coverage for a
dirty editor followed by a workingDir change.

Comment on lines +516 to +522
<section className="db-ui-main" hidden={!showMain} aria-label="database workspace">
{narrow ? (
<div className="db-ui-backbar">
<BackButton onClick={() => setDrilled(false)} label="back to the table list" />
<span className="title">{mode === 'data' && selectedTable ? selectedTable : mode}</span>
</div>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move focus after the back action.

BackButton renders inside .db-ui-main. Clicking it sets drilled to false, so showMain becomes false and the same button's container receives hidden. The focused element is then removed from the accessibility tree, and focus falls back to <body>. A keyboard user loses the position and must tab from the start of the pane.

Move focus to the table list when the user drills out.

♿ Proposed fix
+  const sideRef = useRef<HTMLElement | null>(null)
+  const drillOut = () => {
+    setDrilled(false)
+    // Focus follows the pane that becomes visible.
+    requestAnimationFrame(() => sideRef.current?.focus())
+  }

Then attach the ref and a tab stop to the aside, and use drillOut in the back bar:

-              <aside className={`db-ui-side${passiveAside ? ' passive' : ''}`} hidden={!showSide} aria-label="table list">
+              <aside
+                className={`db-ui-side${passiveAside ? ' passive' : ''}`}
+                hidden={!showSide}
+                aria-label="table list"
+                ref={sideRef}
+                tabIndex={-1}
+              >
-                  <BackButton onClick={() => setDrilled(false)} label="back to the table list" />
+                  <BackButton onClick={drillOut} label="back to the table list" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<section className="db-ui-main" hidden={!showMain} aria-label="database workspace">
{narrow ? (
<div className="db-ui-backbar">
<BackButton onClick={() => setDrilled(false)} label="back to the table list" />
<span className="title">{mode === 'data' && selectedTable ? selectedTable : mode}</span>
</div>
) : null}
<section className="db-ui-main" hidden={!showMain} aria-label="database workspace">
{narrow ? (
<div className="db-ui-backbar">
<BackButton onClick={drillOut} label="back to the table list" />
<span className="title">{mode === 'data' && selectedTable ? selectedTable : mode}</span>
</div>
) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/src/page/index.tsx` around lines 516 - 522, Update the
narrow-mode back navigation around BackButton so drilling out moves focus to the
table-list aside after setDrilled(false) hides db-ui-main. Add the appropriate
ref and keyboard focus target to the aside, and route the BackButton through a
drillOut handler that clears drilled state and focuses the table list.

Comment thread database/ui/styles.css
Comment on lines +492 to +507
/* Derived inks, not the raw tokens: --color-warn (#a87a00) and --color-alert
(#ff0026) sit near 3:1 on these surfaces — below AA for 11px functional
text. Same families, pushed to ≥4.5:1 per theme (the console shell darkened
its accent token for the identical reason). :where keeps the scope prefix
first without adding specificity, so source order settles light vs dark.
One rule per surface — the build's scope checker splits grouped selectors
on commas. */
[data-iii-ui="database"] .db-sql-warn:where([data-theme="dark"] *) { color: #d49b00; }
[data-iii-ui="database"] .db-health-note.warn:where([data-theme="dark"] *) { color: #d49b00; }
[data-iii-ui="database"] .db-health-blocked:where([data-theme="dark"] *) { color: #d49b00; }
[data-iii-ui="database"] .db-plan-warncount:where([data-theme="dark"] *) { color: #d49b00; }
[data-iii-ui="database"] .db-erd-req:where([data-theme="dark"] *) { color: #d49b00; }
[data-iii-ui="database"] .db-sql-warn.ddl:where([data-theme="dark"] *) { color: #ff4d63; }
[data-iii-ui="database"] .db-tree-msg.alert:where([data-theme="dark"] *) { color: #ff4d63; }
[data-iii-ui="database"] .db-msg.alert:where([data-theme="dark"] *) { color: #ff4d63; }
[data-iii-ui="database"] .db-change-op.op-delete:where([data-theme="dark"] *) { color: #ff4d63; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: For each dark-theme derived-ink selector, report the line of the dark
# override and the lines of any later light declaration of the same selector.
set -euo pipefail

f=$(fd -p 'database/ui/styles.css' | head -n1)
echo "file: $f"

for sel in 'db-sql-warn' 'db-health-note.warn' 'db-health-blocked' 'db-plan-warncount' \
           'db-erd-req' 'db-sql-warn.ddl' 'db-tree-msg.alert' 'db-msg.alert' \
           'db-change-op.op-delete'; do
  echo "=== .$sel ==="
  rg -nF ".$sel" "$f"
done

echo "=== focus / ring tokens referenced ==="
rg -n -- '--color-rule-focus|--color-ring|--color-edge' "$f" | head -n 20
echo "=== token definitions across the repo ==="
rg -n -- '--color-rule-focus\s*:' --glob '*.css'

Repository: iii-hq/workers

Length of output: 3509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f='database/ui/styles.css'

echo "file: $f"
wc -l "$f"

echo "=== dark derived-ink block context (lines 460-515) ==="
sed -n '460,515p' "$f" | nl -ba -v460

echo "=== light rule contexts around later matches ==="
for start in 760 800 980 1040 1060 1100 1420; do
  end=$((start + 30))
  echo "--- $f:${start}-${end} ---"
  sed -n "${start},${end}p" "$f" | nl -ba -v$start
done

echo "=== CSS specificity/source-order probe ==="
python3 - <<'PY'
from pathlib import Path
import re
p = Path("database/ui/styles.css")
text = p.read_text()

matches = [(i, m.group(0)) for i, m in enumerate(re.finditer(r'(\[[^\]]+\]\s+[^{]+){([^}]*)}', re.DOTALL, 0), text)]
dark = [(i, rule) for i, rule in matches if ":where([data-theme=\"dark\" ])" in rule]
dark_map = {}
for rule in dark:
    # extract simplest target class from rule text in deterministic text-order; not parsed but matches selectors listed.
    selector = rule[1].split('{',1)[0].strip().replace('\\n',' ')
    if 'color:' in rule[1].split('{',1)[1]:
        dark_map[selector.strip()] = (rule[0], selector)
print("dark rules:")
for rule in dark:
    print(rule[1])

# Find exact selector lines after dark rules for base class .foo and compound foo.bar.
for s in ["[data-iii-ui=\"database\"] .db-sql-warn", "[data-iii-ui=\"database\"] .db-sql-warn.ddl"]:
    # CSS specificity: :where zero, so dark selector 0,0,2 because attribute + class/classish;
    # light same selector has same component counts but later source order => wins for equal specificity.
    found = [(i+1, line) for i,line in enumerate(text.splitlines()) if s in line and "color:" in line]
    print(f"later occurrences of {s}:")
    for f in found:
        print(f"{f[0]}: {f[1]}")
PY

Repository: iii-hq/workers

Length of output: 305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f='database/ui/styles.css'

echo "file: $f"
wc -l "$f"

echo "=== dark derived-ink block context (lines 460-515) ==="
sed -n '460,515p' "$f"

echo "=== selector occurrences around dark and light rule contexts ==="
for start in 380 480 560 760 790 805 970 1035 1055 1070 1105 1415 1455; do
  end=$((start + 20))
  echo "--- $f:${start}-${end} ---"
  sed -n "${start},${end}p" "$f"
done

echo "=== source-order/specificity probe ==="
python3 <<'PY'
from pathlib import Path
import re

p = Path("database/ui/styles.css")
text = p.read_text()
lines = text.splitlines()

# Extract CSS rules that contain ':where([data-theme="dark" ])' and only color declarations after that.
pattern = re.compile(r'(\[[^\]]+\]\s+\.[^]+?):where\(\[data-theme="dark"\s+\]\s+\*\)\s*\{([^}]+)\}', re.DOTALL)
dark = []
for offset, rule in pattern.findall("".join(lines)):
    start_line = text[: sum(len(line)+1 for line in lines[:offset]) if offset else 0].count('\n') + 1
    decl = re.search(r'color:\s*([^;]+);', rule[1])
    if decl:
        dark.append((start_line, offset, decl.group(1).strip(), rule[0]))

# Exact later/light matches for the two selectors explicitly called out in the original comment.
targets = [
    '[data-iii-ui="database"] .db-sql-warn',
    '[data-iii-ui="database"] .db-sql-warn.ddl',
]
for i, line0 in enumerate(dark):
    _, offset, _, sel = line0
    if sel in targets:
        print(f"dark {sel} starts near line {line0[0]} with color {line0[2]}")
        later = []
        for j, line in enumerate(lines, 1):
            if j > line0[0] and sel in line and "color:" in line:
                later.append((j, line.strip()))
        print("later matching declarations:")
        for j, line in later:
            print(f"  {j}: {line}")

# Base CSS specificity is equal for each pair with identical component counts:
# attribute selector + class(es) → 0,1,1 (or 0,1,2 for compound), and :where contributes 0.
# Browser source-order tiebreaker means the later rule wins.
print("later occurrences found for each target:")
for target in targets:
    hits = [(j, line.strip()) for j, line in enumerate(lines, 1) if target in line and "color:" in line]
    print(target, len(hits))
    for j, line in hits:
        print(f"  {j}: {line}")
PY

Repository: iii-hq/workers

Length of output: 16872


Move the dark-theme ink overrides after the light rules.

The two .db-sql-warn selectors have matching specificity and later light declarations override the dark inks. Move this derived-ink block after the panel sections, or place each dark override immediately after its light counterpart.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/ui/styles.css` around lines 492 - 507, Move the dark-theme
derived-ink overrides for the database selectors, including `.db-sql-warn`,
after the corresponding light-theme rules so their matching-specificity
declarations take effect in dark mode. Preserve the existing colors and selector
scope, changing only the rule ordering or adjacency as needed.

@sergiofilhowz
sergiofilhowz merged commit 0fc475b into main Aug 7, 2026
107 of 111 checks passed
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.

2 participants