feat(console): new ui improvements - #733
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
📝 WalkthroughWalkthroughChangesThis PR redesigns multiple console extension pages around shared Console extension workspaces
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
memory/ui/src/page/BankRail.tsx (2)
56-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlign the empty-state copy with the page voice.
The rest of the page uses lowercase prose.
memory/ui/src/page/index.tsxlines 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 winAnnounce the loading skeleton as busy.
The skeleton classes and width helpers are defined, but the
aria-hiddenskeleton hides the loading state from screen readers untilinitialLoadends. Setaria-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 valueConsider extracting the shared page helpers into one module.
useContainerNarrowhere is byte-identical touseContainerNarrowinstate/ui/src/page/browser.tsx(lines 95-115), andconfirmDiscardrepeats the implementation atstate/ui/src/page/browser.tsxlines 162-163.readStored/writeStoredalso duplicate the best-effortlocalStoragepattern used inmemory/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 valueUse
PanelSideinStateBrowser.
PanelSideis exported from@iii-dev/console-uias'left' | 'right', andPageRenderProps['panelSide']uses that alias. ImportPanelSideinstate/ui/src/page/browser.tsxand 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
memory/ui/page.tsxmemory/ui/src/page/BankRail.tsxmemory/ui/src/page/MemoriesPanel.tsxmemory/ui/src/page/MemoryGraph.tsxmemory/ui/src/page/ModeToggle.tsxmemory/ui/src/page/RecallPanel.tsxmemory/ui/src/page/RulesPanel.tsxmemory/ui/src/page/icons.tsxmemory/ui/src/page/index.tsxmemory/ui/src/page/widgets.tsxmemory/ui/styles.csspackages/console-ui/index.d.tsshell/ui/page.tsxshell/ui/src/page/index.tsxstate/ui/page.tsxstate/ui/src/lib/widgets.tsxstate/ui/src/page/ItemView.tsxstate/ui/src/page/ItemsView.tsxstate/ui/src/page/ScopesView.tsxstate/ui/src/page/ValueEditor.tsxstate/ui/src/page/browser.tsxstate/ui/src/page/index.tsxstate/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
| font-family: var(--font-mono, ui-monospace, monospace); | ||
| font-size: 11.5px; | ||
| color: var(--color-ink-faint); | ||
| word-break: break-word; |
There was a problem hiding this comment.
📐 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: replaceword-break: break-wordwithoverflow-wrap: anywherein.mem-ui-banner .detail.memory/ui/styles.css#L447-L447: replaceword-break: break-wordwithoverflow-wrap: anywherein.mem-ui-error-panel p.memory/ui/styles.css#L820-L820: replaceword-break: break-wordwithoverflow-wrap: anywherein.mem-ui-prompt-body.state/ui/styles.css#L291-L291: replaceword-break: break-wordwithoverflow-wrap: anywherein.state-ui-error-panel p.state/ui/styles.css#L403-L403: replaceword-break: break-wordwithoverflow-wrap: anywherein.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-L447memory/ui/styles.css#L820-L820state/ui/styles.css#L291-L291state/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
| 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 |
There was a problem hiding this comment.
🎯 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: applywithinBase(workingDir)before using it in the fallback chain, for exampleconst chat = workingDir && withinBase(workingDir) ? workingDir : nulland thenpersisted ?? 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 beforechangeRoot(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.
| 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 | ||
| } |
There was a problem hiding this comment.
🎯 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.
skill-check — worker0 verified, 55 skipped (no docs/).
Four for four. Nicely done. |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (7)
computer/ui/src/page/index.tsx (1)
45-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the duplicate narrow layout hook to a shared package if the threshold is stable.
computer/ui/src/page/index.tsxrepeats the hook found inbrowser/ui/src/page/index.tsxandgithub/ui/src/page/narrow.ts. The three implementations share the same threshold where they are duplicated, but other pages define differentNARROW_BELOWvalues, 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 valueConsider importing the shared narrow helper instead of copying it.
useContainerNarrowis byte-identical to the copies inbrowser/ui/src/page/index.tsx,database/ui/src/page/index.tsx,github/ui/src/page/index.tsx, andgithub/ui/src/page/GitGraph.tsx.github/ui/src/page/narrow.tsalready exports this helper. Move the hook to one shared module and import it in each page. KeepNARROW_BELOWper 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 valueScope the skeleton width classes under
.wt-ui-skel.
.bar,.w40,.w60, and.w75are generic names. These rules match any descendant of the worktree wrapper, including markup rendered by@iii-dev/console-uicomponents. Nest them under.wt-ui-skelas 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 valueScope the skeleton width classes under
.gh-ui-skel.
.bar.w30through.bar.w90match any element in the github UI that carries abarclass. The sibling rule at line 129 is already scoped to.gh-ui-skel .bar. Use the same scope for the width modifiers so a futurebarclass 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 valueConsider 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
BackButtonwhen 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 valueConsider sharing
useContainerNarrowinstead of copying it.
useContainerNarrowis now duplicated verbatim indatabase/ui/src/page/index.tsx,browser/ui/src/page/index.tsx,computer/ui/src/page/index.tsx,github/ui/src/page/narrow.ts, andworktreepages.github/ui/src/page/narrow.tsalready exports it next toNARROW_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 valueMove 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
📒 Files selected for processing (38)
browser/ui/page.tsxbrowser/ui/src/lib/widgets.tsxbrowser/ui/src/page/SessionRail.tsxbrowser/ui/src/page/SessionView.tsxbrowser/ui/src/page/Viewport.tsxbrowser/ui/src/page/index.tsxbrowser/ui/styles.csscomputer/ui/page.tsxcomputer/ui/src/lib/widgets.tsxcomputer/ui/src/page/SessionRail.tsxcomputer/ui/src/page/index.tsxcomputer/ui/styles.cssconsole/web/package.jsonconsole/web/src/App.tsxconsole/web/src/components/chat/LexicalShell.tsxconsole/web/src/components/workspace/pane-controls.tsxconsole/web/src/index.cssconsole/web/src/pages/Ext/index.tsxconsole/web/src/types/injectable-ui.tsdatabase/ui/page.tsxdatabase/ui/src/page/SqlPanel.tsxdatabase/ui/src/page/index.tsxdatabase/ui/styles.cssgithub/ui/page.tsxgithub/ui/src/page/GitGraph.tsxgithub/ui/src/page/GithubPage.tsxgithub/ui/src/page/icons.tsxgithub/ui/src/page/index.tsxgithub/ui/src/page/narrow.tsgithub/ui/src/page/result-views.tsxgithub/ui/styles.cssworktree/ui/page.tsxworktree/ui/src/page/WorktreeDetailPanel.tsxworktree/ui/src/page/WorktreeGraph.tsxworktree/ui/src/page/icons.tsxworktree/ui/src/page/index.tsxworktree/ui/src/page/worktree-data.tsworktree/ui/styles.css
| [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; | ||
| } |
There was a problem hiding this comment.
📐 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.
| [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
| {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} |
There was a problem hiding this comment.
🎯 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.
| {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.
| [data-iii-ui="computer"] .cp-ui-banner > span { | ||
| word-break: break-word; | ||
| } |
There was a problem hiding this comment.
📐 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.
| [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
| tabId={tabId} | ||
| onRequestClose={onClose} | ||
| onMissing={onExtMissing} | ||
| workingDir={active?.workingDir ?? null} |
There was a problem hiding this comment.
🗄️ 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 shellRepository: 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 || trueRepository: 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.tsxRepository: 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 || trueRepository: 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.
| <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} |
There was a problem hiding this comment.
📐 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.
| <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.
| /* 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; } |
There was a problem hiding this comment.
🎯 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]}")
PYRepository: 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}")
PYRepository: 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.
Improvements on the UI
Summary by CodeRabbit