feat(tech-spec): devex revamp#1914
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (96)
✅ Files skipped from review due to trivial changes (13)
🚧 Files skipped from review as they are similar to previous changes (70)
📝 WalkthroughWalkthroughThis PR adds the tech-specs workspace scaffolding, a gallery site, a new devexp presentation app, and markdown specs covering the architecture, runtime, migration, and rollout model. ChangesTech-specs Overhaul
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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: 7
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (14)
tech-specs/README.md-9-33 (1)
9-33: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd language tags to the fenced blocks.
Lines 9, 22, and 31 use bare code fences, which trips
markdownlintMD040. Tag the directory-tree examples astextand the command example asbashso the docs lint cleanly.🤖 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 `@tech-specs/README.md` around lines 9 - 33, Add language tags to the fenced examples in README content so markdownlint passes: update the directory-tree code blocks to use a plain text language tag and the command example to use a bash language tag. Locate the affected fences in the README sections describing the tech-specs layout, built output, and the add-a-presentation command, and make sure every fenced block in those examples has an explicit language identifier.Source: Linters/SAST tools
tech-specs/_gallery/src/hooks/useTheme.ts-15-19 (1)
15-19: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winApply the theme attribute before first paint.
readTheme()already knows the persisted theme synchronously, butdata-themeis only written in an effect after mount. Returning dark-mode users will see a light-theme flash on every reload until that effect runs.🤖 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 `@tech-specs/_gallery/src/hooks/useTheme.ts` around lines 15 - 19, The theme is being applied too late because useTheme only writes document.documentElement.dataset.theme inside useEffect after mount, causing a flash on reload. Update useTheme so the persisted theme from readTheme() is applied before first paint, ideally by setting the document root theme synchronously during initialization or in a pre-paint hook, while keeping the existing theme state managed by useState and referencing useTheme/readTheme/document.documentElement.dataset.theme.tech-specs/_gallery/src/index.css-4-7 (1)
4-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winQuote the named fallback fonts.
Lines 4-7 are tripping
value-keyword-casebecause the named fallbacks are unquoted. Quoting them preserves the intended family names and clears the lint errors.Suggested fix
- --font-sans: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, - Consolas, "Liberation Mono", "Courier New", monospace; - --font-mono: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, - Consolas, "Liberation Mono", "Courier New", monospace; + --font-sans: "Chivo Mono", ui-monospace, "SFMono-Regular", "Menlo", "Monaco", + "Consolas", "Liberation Mono", "Courier New", monospace; + --font-mono: "Chivo Mono", ui-monospace, "SFMono-Regular", "Menlo", "Monaco", + "Consolas", "Liberation Mono", "Courier New", monospace;🤖 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 `@tech-specs/_gallery/src/index.css` around lines 4 - 7, The CSS font stack in the index stylesheet is using unquoted named fallback families, which is causing the lint error. Update the font declarations for the variables in the root scope so every named fallback in the stack is quoted consistently, while keeping the same fallback order and the existing Chivo Mono primary family.Source: Linters/SAST tools
tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts-20-25 (1)
20-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winClamp empty steppers before computing
total - 1.If
totalis ever0, Lines 56 and 69 can setstepto-1. Downstream consumers likeMeetingPointsSequenceindex arrays withstepper.step, so this hook can publish an invalid state instead of a safe empty-step fallback.Suggested fix
export function useStepper(total: number, intervalMs = 2400, autoPlay = false): Stepper { + const maxStep = Math.max(total - 1, 0) const [step, setStep] = useState(0) - const [playing, setPlaying] = useState(autoPlay) + const [playing, setPlaying] = useState(autoPlay && total > 0) const timer = useRef<ReturnType<typeof setInterval> | null>(null) - const atEnd = step >= total - 1 + const atEnd = step >= maxStep @@ - if (s >= total - 1) { + if (s >= maxStep) { setPlaying(false) return s } @@ - setStep((s) => (s >= total - 1 ? 0 : s)) + setStep((s) => (s >= maxStep ? 0 : s)) setPlaying(true) - }, [total]) + }, [maxStep]) @@ - setStep((s) => Math.min(total - 1, s + 1)) - }, [total]) + setStep((s) => Math.min(maxStep, s + 1)) + }, [maxStep]) @@ - setStep(Math.max(0, Math.min(total - 1, s))) + setStep(Math.max(0, Math.min(maxStep, s))) }, - [total], + [maxStep], )Also applies to: 54-70
🤖 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 `@tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts` around lines 20 - 25, The useStepper hook currently computes atEnd from total - 1 and can drive step to -1 when total is 0, which leaks an invalid state to consumers like MeetingPointsSequence. Update useStepper to explicitly handle the empty-step case before any total - 1 logic, and ensure the reset/advance behavior in the step-setting logic keeps step clamped to 0 when total is zero while preserving the existing API around step, playing, and timer handling.tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx-103-106 (1)
103-106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winScope the “no seed, no pointer” claim more narrowly.
The current copy overstates the contract. The documented bootstrap flow still has a seed-only worker config block and then replaces it with a breadcrumb to the persisted value, so this wording reads as contradictory unless you explicitly limit it to
worker-compose.yml/iii.worker.yaml.As per path instructions,
config.yamlworker blocks are seed-only and later replaced with a breadcrumb to./data/configuration/<id>.yaml.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx` around lines 103 - 106, The StatusPanel copy in ConfigSection is too broad and conflicts with the documented bootstrap flow. Update the headline/detail text to scope the “no seed, no pointer” claim only to worker-compose.yml and iii.worker.yaml, and explicitly reflect that config.yaml worker blocks are seed-only before being replaced with a breadcrumb to ./data/configuration/<id>.yaml. Use StatusPanel, headline, and detail as the anchors when revising the wording.Source: Path instructions
tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx-60-62 (1)
60-62: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign the JSON error envelope with the documented contract.
kindhere conflicts with the established error shape and omitserror_id. If consumers follow this slide, they’ll parse a field the HTTP/docs contract does not guarantee.Suggested update
- under --json, errors also emit {'{ error: { code, kind, message } }'}. + under --json, errors also emit {'{ error: { code, message, error_id? } }'}.As per path instructions,
docs/changelog/index.mdxandengine/src/workers/rest_api/README.mdestablish the stable envelope as{"error":{"code","message","error_id"?}}.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx` around lines 60 - 62, The JSON error example in PlaygroundPage should match the stable documented error envelope instead of showing a non-contract field. Update the text in the PlaygroundPage content so it reflects {"error":{"code","message","error_id"?}} and remove the `kind` field from the example, keeping the wording aligned with the contract used in the changelog and REST API README.Source: Path instructions
tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx-24-25 (1)
24-25: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse
pnpmin the canonical worker scripts example.This slide currently teaches
npm install/npm run dev, which conflicts with the repo’s JS/TS package-manager standard and will drift from the rest of the devex material. The same snippet appears again inComposeSection.tsx, so both copies should move together.Suggested update
- {' '}scripts: {'{ '}install: npm install, start: npm run dev {'}'}{'\n'} + {' '}scripts: {'{ '}install: pnpm install, start: pnpm run dev {'}'}{'\n'}As per coding guidelines, use
pnpm(nevernpm) for JS/TS packages.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx` around lines 24 - 25, The canonical worker scripts example still uses npm commands, which conflicts with the repo standard; update the example in ComposePage and the matching snippet in ComposeSection to use pnpm consistently. Locate the worker script text in the ComposePage component and the duplicated example in ComposeSection, and replace the install/start commands so the slide teaches pnpm-based workflows only.Source: Coding guidelines
tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx-79-82 (1)
79-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve original casing in spec row descriptions.
This wrapper forces all row content to lowercase, which will mangle case-sensitive identifiers and examples in the tech-spec UI.
Suggested fix
- <div className="mt-0.5 font-mono text-[12px] leading-[1.6] text-ink-faint lowercase"> + <div className="mt-0.5 font-mono text-[12px] leading-[1.6] text-ink-faint"> {children} </div>🤖 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 `@tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx` around lines 79 - 82, The SpecSheet row description wrapper is forcing all child content to lowercase, which breaks case-sensitive identifiers and examples. Update the children rendering in SpecSheet so it preserves the original casing by removing the lowercase styling from that div, while keeping the existing typography and layout classes intact.tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx-59-68 (1)
59-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExpose the active section to assistive tech.
Right now the current section is only indicated by color. Add
aria-current="location"on the active link so screen readers receive the same state.Suggested fix
<a key={link.id} href={`#${link.id}`} + aria-current={active === link.id ? 'location' : undefined} className={cn( 'font-mono text-[12px] lowercase py-1.5 transition-colors whitespace-nowrap', active === link.id ? 'text-accent' : 'text-ink-faint hover:text-ink',🤖 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 `@tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx` around lines 59 - 68, The active section is only distinguished visually in TopNav’s link rendering, so add an accessible state to the active anchor in the link map. Update the conditional rendering around the active link check in TopNav to set aria-current="location" whenever active matches link.id, while leaving the existing className styling and href behavior unchanged.tech-specs/2026-06-devexp/presentation/src/index.css-204-221 (1)
204-221: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winHonor reduced-motion for the blinking caret too.
Line 204 disables most motion, but not
.blink. Sincetech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsxalways applies that class, users withprefers-reduced-motion: reducestill get a continuous flashing animation.Suggested fix
`@media` (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } + .blink, .flow-dash, .flow-dash-slow, .ripple-ring, .pulse-dot, .wiggle,🤖 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 `@tech-specs/2026-06-devexp/presentation/src/index.css` around lines 204 - 221, The reduced-motion media query in the stylesheet disables several animations but leaves the blinking caret active, so users can still see flashing motion. Update the prefers-reduced-motion block in the CSS to also target the blink class used by Caret so its animation is removed or neutralized when reduced motion is requested, keeping the behavior consistent with the other motion-reduction rules.tech-specs/2026-06-devexp/presentation/src/content/changes.ts-342-348 (1)
342-348: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winInclude the other implicit roots in the readiness graph data.
ReadinessGraphonly renders roots that exist inREADINESS_NODES, butReadinessSectionlater saysconfiguration,iii-process-daemon, andiii-worker-opsare all valid always-readydepends_ontargets. Keeping onlyconfigurationhere makes the graph understate that contract. Add the missing roots or narrow the downstream copy.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/content/changes.ts` around lines 342 - 348, The readiness graph data only declares one always-ready root, but ReadinessSection treats iii-process-daemon and iii-worker-ops as valid always-ready depends_on targets too. Update READINESS_NODES in changes.ts to include those missing implicit roots, or tighten the later copy so it only mentions roots actually present in the graph. Keep the labels/notes consistent with the existing readiness node entries so ReadinessGraph and the section text agree.tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx-23-45 (1)
23-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDrop the tab roles or implement real tab semantics.
Line 24 and Line 33 expose this as a tablist, but the buttons only use
aria-pressedand click handling. Screen readers will announce tabs that don't behave like tabs. Either switch this to a plain button group, or add the missing tab behavior (aria-selected, roving focus, arrow-key navigation, and tabpanel wiring).Suggested minimal fix if this is only a visual mode switch
<div - role="tablist" className={cn('inline-flex border border-rule p-[2px]', className)} > {options.map((opt) => { const active = opt.value === value return ( <button key={opt.value} type="button" - role="tab" aria-pressed={active} onClick={() => onChange(opt.value)} className={cn(🤖 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 `@tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx` around lines 23 - 45, The ModeToggle component is exposing tab semantics without implementing real tabs. Update the ModeToggle render so it no longer uses tablist/tab roles and aria-pressed on the buttons unless you add full tab behavior; if this is just a visual mode switch, make it a plain button group with the existing click handling and styling. Use the ModeToggle component, the options.map rendering, and the button element as the main places to adjust the accessibility semantics.tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx-45-47 (1)
45-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRender falsy outputs explicitly.
output ? ...drops valid terminal payloads like0and empty strings, so those rows disappear instead of rendering.Proposed fix
- {output ? ( + {output !== null && output !== undefined ? ( <div className="pl-4 text-[12.5px] text-ink-faint">{output}</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 `@tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx` around lines 45 - 47, The Terminal component is using a truthy check on output, which hides valid falsy terminal values like 0 and empty strings. Update the conditional rendering in Terminal to explicitly check for the presence of output rather than its truthiness so that valid payloads still render, and keep the existing output display block intact.tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx-77-89 (1)
77-89: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winGuard the empty-track case.
This component dereferences
tracks[0]in several places, so an empty array crashes the whole view on first render. Either require a non-empty tuple in the prop type or return a fallback before initializing state.Proposed fix
interface CliPlaygroundProps { - tracks: PlayTrack[] + tracks: [PlayTrack, ...PlayTrack[]] className?: string autoPlay?: boolean }export function CliPlayground({ tracks, className, autoPlay }: CliPlaygroundProps) { + if (tracks.length === 0) return null const [trackId, setTrackId] = useState(tracks[0].id)🤖 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 `@tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx` around lines 77 - 89, CliPlayground currently assumes tracks always has at least one item and immediately reads tracks[0], which will crash on an empty array. Update CliPlaygroundProps and the CliPlayground component to either enforce a non-empty tracks tuple at the type level or return a safe fallback before calling useState/useMemo/useStepper. Make sure the guard happens before any access to tracks[0], track.steps, or setTrackId initialization so the component can render safely when no tracks are provided.
🧹 Nitpick comments (23)
tech-specs/2026-06-devexp/README.md (1)
338-345: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd language specifier to fenced code block.
The code block at line 338 lacks a language specifier. Add
yamlortextto satisfy markdownlint and improve syntax highlighting.- ``` + ```yaml Phase 0 Dual-parser — compose lowered to today's EngineConfig (no behavior change, --compose flag)🤖 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 `@tech-specs/2026-06-devexp/README.md` around lines 338 - 345, The fenced block in the README phase list is missing a language specifier, so update the markdown around the Phase 0–Phase 5 snippet to use an explicit fence like yaml or text. Locate the block by the phase labels in the spec document and adjust the opening fence only, keeping the existing content unchanged so markdownlint passes and syntax highlighting is enabled.Source: Linters/SAST tools
tech-specs/2026-06-devexp/lifecycle-and-onboarding.md (11)
179-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
textlanguage hint to hot-reload output.The
iii logs -fstream output is plain text. Tag astextto satisfy MD040.-``` +```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 179 - 184, The hot-reload output example in the lifecycle/onboarding spec is missing the `text` language hint required by MD040. Update the fenced code block in the affected documentation so the `iii logs -f` output is explicitly marked as text, using the existing example block as the target to fix.
107-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
yamllanguage hint to compose example.The
worker-compose.ymlexample at lines 107–118 lacks a language tag. Addyamlfor syntax highlighting and MD040 compliance.-``` +```yaml
🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 107 -
118, The compose example in the onboarding doc is missing the YAML language
hint, so update the fenced block around the worker-compose example to use the
same `yaml` tag already shown in the snippet; this is the only change needed for
the example associated with the worker compose configuration.
355-363: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to crash recovery output.
The crashed worker output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 355 -
363, The crash recovery example in the lifecycle/onboarding spec is missing a
language hint on the fenced block, so update the crash output snippet to use the
correct markdown code fence language in the example around the worker crash log.
Make this change in the section containing the caller-worker restart output so
the rendered example is tagged as plain text and matches the MD040 requirement.
423-427: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to lock fallback output.
The lock fallback output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 423 -
427, The lock fallback output in this section is missing a language hint, which
triggers the Markdown lint rule. Update the fenced block that shows the fallback
message under the state output to use a text language tag so the
`state`/`iii.lock` example is marked as plain text and satisfies MD040.
400-404: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to cycle error output.
The cycle error output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 400 -
404, The cycle error output block is missing an explicit language hint, so
update the markdown snippet in the lifecycle-and-onboarding content to tag the
plain-text error example as text for MD040 compliance. Locate the cycle error
example near the compose dependency cycle message and change the fenced block to
use the text language identifier while keeping the existing error content
unchanged.
408-415: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to port conflict output.
The port conflict error output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 408 -
415, The port conflict example in the lifecycle-and-onboarding docs is missing a
language hint, so update the fenced block around the ws://localhost bind error
output to use the text language tag. Locate the snippet containing the “cannot
bind ws://localhost:49134 — address already in use” message and change its
opening fence to a text-labeled fence so it satisfies MD040.
378-382: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to orphan sweep output.
The orphan sweep output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 378 -
382, Add the missing text language hint to the orphan sweep output block in the
lifecycle-and-onboarding spec so the plain-text example is tagged correctly for
MD040. Update the fenced block around the orphaned worker process output to use
the appropriate text qualifier, keeping the existing content in place and
adjusting only the markdown fence formatting in that section.
46-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to terminal output block.
The iii up target output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 46 - 59,
The terminal output block in the lifecycle-and-onboarding spec is missing the
required language hint, so update the fenced output example to use the text
label. Locate the plain output snippet shown for the iii up command and change
its code fence so it is explicitly tagged as text, satisfying MD040 without
altering the content.
91-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to file tree listing.
The iii init quickstart output is a file tree. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 91 - 103,
The quickstart file tree in the lifecycle-and-onboarding spec is missing an
explicit text language hint for the Markdown code block. Update the `iii init
quickstart` output snippet so the file tree fence is tagged as text, using the
existing quickstart listing in the document to locate the block, and keep the
rest of the tree unchanged.
323-329: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to iii ps output.
The iii ps target output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 323 -
329, The `iii ps` example output is currently an unlabeled fenced block, which
triggers the MD040 lint rule. Update the fenced code block in the `iii ps`
section to use the `text` language hint so the plain-text sample is explicitly
tagged, keeping the content unchanged and ensuring the block is easy to locate
by its `iii ps`/process listing example.
386-393: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add text language hint to error output.
The missing dependency error output is plain text. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 386 -
393, The compose error example is missing a language hint, so update the fenced
code block in the lifecycle-and-onboarding markdown to use the text language
tag. Keep the existing error output content, and change the block that shows the
worker-compose.yml dependency error so it is rendered as plain text for MD040
compliance.
tech-specs/2026-06-devexp/cli-and-functions.md (2)
132-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd language hint to CLI tree diagram.
The command tree at line 132 is plain text. Tag as
textto satisfy MD040.-``` +```text
🤖 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 `@tech-specs/2026-06-devexp/cli-and-functions.md` at line 132, The CLI tree
diagram in the markdown spec is missing a language hint, so update the fenced
block around the command tree to use the text info string to satisfy MD040.
Locate the diagram in the CLI-and-functions documentation and change the
existing fence opening so the tree content is explicitly marked as text; keep
the diagram content unchanged.
272-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add language hint to architecture pseudo-code.
The thin-wrapper architecture block is pseudo-code. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/cli-and-functions.md` around lines 272 - 278, The
architecture pseudo-code block is missing an explicit language hint, so update
the fenced block in the CLI/functions spec to use a text label. Locate the
pseudo-code diagram around the worker parse/serialize/invoke flow and change the
opening fence to the text-annotated form so it satisfies MD040 while preserving
the existing content.
tech-specs/2026-06-devexp/process-daemon.md (3)
289-302: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd language hint to function surface spec.
The
process::*function surface block is pseudo-code with Rust-like struct syntax. Tag asrustortextto satisfy MD040.-``` +```rust
🤖 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 `@tech-specs/2026-06-devexp/process-daemon.md` around lines 289 - 302, The
process function surface block is missing a language hint, so update the fenced
pseudo-code snippet in the process-daemon spec to use a Rust or plain-text
label. Keep the existing process::start/process::stop/process::restart and
related function signatures unchanged, and only adjust the code fence marker so
it satisfies MD040.
96-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add language hint to plain-text diagram.
The tier diagram at lines 96–104 lacks a language tag. Add text to satisfy MD040 and clarify it's not executable code.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/process-daemon.md` around lines 96 - 104, The tier
diagram block in process-daemon.md is missing a language hint, so update the
fenced diagram under the relevant section to use the text language tag. Keep the
existing content unchanged and ensure the fence around the TIER 0/1/2 diagram is
marked as text so the Markdown renderer and MD040 treat it as plain text instead
of executable code.
463-481: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Add language hint to recovery sequence.
The daemon restart sequence is pseudo-code. Tag as text to satisfy MD040.
-```
+```text🤖 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 `@tech-specs/2026-06-devexp/process-daemon.md` around lines 463 - 481, The
recovery sequence in the process-daemon spec is a pseudo-code block that needs
an explicit language hint for Markdown compliance. Update the fenced block in
the restart/reconcile section to use a text code fence by tagging the existing
sequence with text, keeping the content unchanged, and verify the surrounding
section still renders correctly in the process::reconcile and daemon restart
flow.
tech-specs/2026-06-devexp/engine-and-gateway.md (1)
238-249: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd language hint to pseudo-code blocks.
Lines 238–249 and 410 are fenced code blocks without language tags. While these are pseudo-code, adding
textorbashas the language hint silences MD040 and improves rendering consistency.-``` +```text
🤖 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 `@tech-specs/2026-06-devexp/engine-and-gateway.md` around lines 238 - 249, Add
language hints to the fenced pseudo-code blocks in the engine-and-gateway spec
so Markdown lint MD040 is silenced and rendering stays consistent. Update the
code fences around the STEP 0 engine startup flow and the later connected-path
pseudo-code (including the block near the referenced second occurrence) to use a
language tag such as text or bash, keeping the content unchanged while matching
the rest of the document’s formatting.
tech-specs/_gallery/src/components/schematic/ModeToggle.tsx (1)
14-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the toggle group's accessible name mandatory.
ModeTogglealways rendersrole="group", butlabelis optional, so future call sites can create an unnamed interactive group. Requiringlabelhere (or supportingaria-labelledby) closes that gap at the API boundary.Proposed change
interface ModeToggleProps<T extends string> { value: T onChange: (next: T) => void options: ModeToggleOption<T>[] className?: string - /** accessible name for the toggle group, e.g. "theme" */ - label?: string + /** accessible name for the toggle group, e.g. "theme" */ + label: string }Also applies to: 29-32
🤖 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 `@tech-specs/_gallery/src/components/schematic/ModeToggle.tsx` around lines 14 - 15, Make the toggle group’s accessible name required in ModeToggle, since it always renders a role="group" and should never be unnamed. Update the ModeToggle prop/type definition to require label (or add explicit aria-labelledby support) and adjust the ModeToggle render logic so every call site must provide a group name. Also review the related usage in ModeToggle’s JSX to ensure the group’s accessible labeling path is enforced consistently.
tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx (1)
6-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid a second source of truth for kind labels.
These labels already live in
src/content/map.tsnext toKIND_HUE. Deriving the legend from that shared data keeps the legend, node sheets, and future copy updates in sync.Possible simplification
-import { KIND_HUE, MEETING_POINTS } from '`@/content/map`' +import { KIND_HUE, KIND_LABEL, MEETING_POINTS } from '`@/content/map`' -const LEGEND: Array<{ kind: keyof typeof KIND_HUE; label: string }> = [ - { kind: 'plane', label: 'connection plane' }, - { kind: 'brain', label: 'the brain' }, - { kind: 'pid', label: 'pid owner' }, - { kind: 'file', label: 'file' }, - { kind: 'wk', label: 'worker' }, -] +const LEGEND = (Object.keys(KIND_HUE) as Array<keyof typeof KIND_HUE>).map( + (kind) => ({ kind, label: KIND_LABEL[kind] }), +)🤖 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 `@tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx` around lines 6 - 12, The legend in SystemMapSection is duplicating kind labels instead of using the shared map data. Update the LEGEND definition to derive its items from the same source used for KIND_HUE in src/content/map.ts, so SystemMapSection stays in sync with node sheets and future label changes. Keep the existing kind keys, but remove the hardcoded label list and build the legend from the shared data model.
tech-specs/2026-06-devexp/presentation/src/content/map.ts (1)
23-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winType the map identifiers as a closed set.
id,from, andtoare all plainstring, butSystemMap/MapDatasheettreat them as if every value is guaranteed to exist. One typo in this dataset turns into a runtime crash instead of a type error.Proposed direction
+export type MapNodeId = + | 'compose' + | 'lock' + | 'gw' + | 'config' + | 'ops' + | 'daemon' + | 'sandbox' + | 'w1' + | 'w2' + | 'w3' + export interface MapNode { - id: string + id: MapNodeId x: number y: number w: number h: number title: string @@ export interface MapEdge { id: string - from: string - to: string + from: MapNodeId + to: MapNodeId d: string label: stringThen carry
MapNodeIdinto theselectedstate inSystemMapSection.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/content/map.ts` around lines 23 - 49, Type the map identifiers as a closed set instead of plain strings in MapNode and MapEdge so invalid ids are caught at compile time. Introduce a shared MapNodeId union derived from the actual node ids in the map dataset, then update id, from, and to to use that type throughout SystemMap, MapDatasheet, and any lookup helpers that assume the ids always exist. Also propagate MapNodeId into the selected state in SystemMapSection so selection can only reference valid nodes.
tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx (1)
30-38: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDefault the shared button primitive to
type="button".As written, this will submit by default when it lands inside a form. Shared UI buttons are safer if they opt out unless the caller explicitly passes
type="submit".Suggested fix
<button ref={ref} + type="button" className={cn( 'inline-flex items-center justify-center gap-x-2 whitespace-nowrap font-mono lowercase rounded-none transition-[background-color,color,border-color] duration-150 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:pointer-events-none disabled:opacity-40 select-none cursor-pointer', variantClasses[variant], sizeClasses[size], className, )} {...props} >🤖 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 `@tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx` around lines 30 - 38, The shared Button primitive currently inherits the browser default submit behavior inside forms, so update the Button component to default its rendered button type to “button” unless the caller explicitly provides a type. Make this change in the Button function/component where props are spread onto the underlying button element, and ensure any caller-supplied type still overrides the default.
tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx (1)
22-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared hero thesis instead of retyping it here.
src/content/changes.tsalready exportsHERO_THESIS, but this paragraph duplicates the same copy locally. That gives the section two sources of truth and makes spec updates easy to miss.🤖 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 `@tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx` around lines 22 - 29, Replace the duplicated hero paragraph in Hero.tsx with the shared HERO_THESIS from src/content/changes.ts so the section uses one source of truth. Update the Hero section to reference the existing exported thesis instead of hardcoding the copy locally, keeping the surrounding markup and emphasis spans aligned with the shared content.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3f10f3d6-8570-47b3-9134-ae71d1bbf19d
⛔ Files ignored due to path filters (3)
tech-specs/2026-06-devexp/presentation/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamltech-specs/_gallery/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamltech-specs/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (96)
tech-specs/.gitignoretech-specs/.nvmrctech-specs/2026-06-devexp/README.mdtech-specs/2026-06-devexp/cli-and-functions.mdtech-specs/2026-06-devexp/configuration-and-bootstrap.mdtech-specs/2026-06-devexp/devex-review-improvements.mdtech-specs/2026-06-devexp/engine-and-gateway.mdtech-specs/2026-06-devexp/lifecycle-and-onboarding.mdtech-specs/2026-06-devexp/migration.mdtech-specs/2026-06-devexp/presentation/.gitignoretech-specs/2026-06-devexp/presentation/README.mdtech-specs/2026-06-devexp/presentation/index.htmltech-specs/2026-06-devexp/presentation/package.jsontech-specs/2026-06-devexp/presentation/src/App.tsxtech-specs/2026-06-devexp/presentation/src/components/Footer.tsxtech-specs/2026-06-devexp/presentation/src/components/PageShell.tsxtech-specs/2026-06-devexp/presentation/src/components/PlayerControls.tsxtech-specs/2026-06-devexp/presentation/src/components/Section.tsxtech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsxtech-specs/2026-06-devexp/presentation/src/components/TopNav.tsxtech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsxtech-specs/2026-06-devexp/presentation/src/components/diagrams/DaemonModel.tsxtech-specs/2026-06-devexp/presentation/src/components/diagrams/MeetingPointsSequence.tsxtech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsxtech-specs/2026-06-devexp/presentation/src/components/diagrams/SystemMap.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Cell.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/CodeBlock.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/FnChip.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Prompt.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Sheet.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/StatusDot.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/StatusPanel.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/Wordmark.tsxtech-specs/2026-06-devexp/presentation/src/components/schematic/WorkerCard.tsxtech-specs/2026-06-devexp/presentation/src/content/changes.tstech-specs/2026-06-devexp/presentation/src/content/map.tstech-specs/2026-06-devexp/presentation/src/content/playground.tstech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.tstech-specs/2026-06-devexp/presentation/src/hooks/useStepper.tstech-specs/2026-06-devexp/presentation/src/hooks/useTheme.tstech-specs/2026-06-devexp/presentation/src/index.csstech-specs/2026-06-devexp/presentation/src/lib/utils.tstech-specs/2026-06-devexp/presentation/src/main.tsxtech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsxtech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsxtech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsxtech-specs/2026-06-devexp/presentation/src/sections/ComposeSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/DaemonSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/Hero.tsxtech-specs/2026-06-devexp/presentation/src/sections/MeetingPointsSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/PayoffSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/PlaygroundSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/ReadinessSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/RemovedSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsxtech-specs/2026-06-devexp/presentation/src/sections/WhySection.tsxtech-specs/2026-06-devexp/presentation/src/vite-env.d.tstech-specs/2026-06-devexp/presentation/tsconfig.app.jsontech-specs/2026-06-devexp/presentation/tsconfig.jsontech-specs/2026-06-devexp/presentation/tsconfig.node.jsontech-specs/2026-06-devexp/presentation/vite.config.tstech-specs/2026-06-devexp/process-daemon.mdtech-specs/2026-06-devexp/secrets.mdtech-specs/2026-06-devexp/worker-compose.mdtech-specs/README.mdtech-specs/_gallery/.gitignoretech-specs/_gallery/index.htmltech-specs/_gallery/package.jsontech-specs/_gallery/src/App.tsxtech-specs/_gallery/src/components/Gallery.tsxtech-specs/_gallery/src/components/PresentationCard.tsxtech-specs/_gallery/src/components/SiteFooter.tsxtech-specs/_gallery/src/components/SiteHeader.tsxtech-specs/_gallery/src/components/schematic/ModeToggle.tsxtech-specs/_gallery/src/components/schematic/Prompt.tsxtech-specs/_gallery/src/components/schematic/Sheet.tsxtech-specs/_gallery/src/components/schematic/StatusDot.tsxtech-specs/_gallery/src/components/schematic/Wordmark.tsxtech-specs/_gallery/src/content/presentations.tstech-specs/_gallery/src/hooks/useTheme.tstech-specs/_gallery/src/index.csstech-specs/_gallery/src/lib/utils.tstech-specs/_gallery/src/main.tsxtech-specs/_gallery/src/vite-env.d.tstech-specs/_gallery/tsconfig.app.jsontech-specs/_gallery/tsconfig.jsontech-specs/_gallery/tsconfig.node.jsontech-specs/_gallery/vite.config.tstech-specs/build.mjstech-specs/package.jsontech-specs/vercel.json
| @@ -0,0 +1,153 @@ | |||
| @import "tailwindcss"; | |||
|
|
|||
| @theme { | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Tailwind v4 directives are still lint-failing here.
Stylelint is flagging @theme and each @utility as unknown, so this file will keep failing static analysis until the lint config is updated to allow Tailwind v4 directives.
#!/bin/bash
set -euo pipefail
echo "== Tailwind v4 directives used by the gallery stylesheet =="
rg -n '`@theme`|`@utility`' tech-specs/_gallery/src/index.css
echo
echo "== Stylelint config candidates =="
fd -HI '(.stylelintrc.*|stylelint.config.*)$' . -x sh -c '
echo "=== $1 ==="
sed -n "1,220p" "$1"
' sh {}
echo
echo "== Existing unknown-at-rule / Tailwind exceptions =="
rg -n 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|tailwindcss' . \
-g '.stylelintrc*' -g 'stylelint.config.*' -g 'package.json'Also applies to: 111-142
🧰 Tools
🪛 Stylelint (17.13.0)
[error] 3-3: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🤖 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 `@tech-specs/_gallery/src/index.css` at line 3, Stylelint is still treating the
Tailwind v4 directives in the gallery stylesheet as unknown at-rules, so this
file will keep failing lint until the config is updated. Update the Stylelint
configuration that governs tech-specs/_gallery/src/index.css to explicitly allow
the Tailwind directives used there, especially `@theme` and `@utility`, and verify
the exception is added in the relevant stylelint config or rule setup rather
than in the stylesheet itself.
| const stepper = useStepper(ORDER.length, 1500) | ||
| const frontier = stepper.step // index of the node currently coming up | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Model the root node as already ready.
READINESS_NODES defines configuration as the always-ready root, but this logic renders it as awaiting L1 on the first frame and reports 0 / 5 ready. The frontier needs to start after the root, otherwise the diagram contradicts its own data model.
Proposed fix
export function ReadinessGraph({ className }: { className?: string }) {
const stepper = useStepper(ORDER.length, 1500)
- const frontier = stepper.step // index of the node currently coming up
+ const frontier = stepper.step + 1 // root is already ready
return (
@@
{ORDER.map((n, i) => {
const ready = i < frontier
const coming = i === frontier
@@
<span className="ml-auto font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint tabular-nums">
- {Math.min(frontier, ORDER.length)} / {ORDER.length} ready
+ {Math.min(frontier, ORDER.length)} / {ORDER.length} ready
</span>Also applies to: 25-27, 89-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
`@tech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsx`
around lines 15 - 17, The readiness animation in ReadinessGraph should treat the
root node as already ready instead of counting it as pending. Update the
frontier initialization/derivation in ReadinessGraph (the useStepper and
frontier logic) so it starts after the always-ready configuration node, and
adjust any readiness counts/labels derived from that frontier to exclude the
root from “awaiting” and “0 / 5 ready” states on the first frame. Make the
change consistently across the rendering and progress calculation paths that use
ORDER, frontier, and the node readiness display.
| /** | ||
| * hash routing with two namespaces: `#/...` paths are routes (deep-dive | ||
| * pages); bare `#section-id` hashes stay native anchor scrolls on the home | ||
| * page. | ||
| */ | ||
| export function useHashRoute(): Route { | ||
| const [route, setRoute] = useState<Route>(() => parse(window.location.hash)) | ||
|
|
||
| useEffect(() => { | ||
| const onChange = () => setRoute(parse(window.location.hash)) | ||
| window.addEventListener('hashchange', onChange) | ||
| return () => window.removeEventListener('hashchange', onChange) | ||
| }, []) | ||
|
|
||
| useEffect(() => { | ||
| // deep-dive pages and the explicit "#/" home link start at the top; | ||
| // bare "#section" hashes keep native anchor behaviour. | ||
| if (route.kind === 'page' || window.location.hash === '#/') { | ||
| window.scrollTo({ top: 0, behavior: 'instant' }) | ||
| } | ||
| }, [route]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Bare #section links won't land on the section when leaving a deep-dive page.
Line 18 says bare hashes should keep native anchor scrolling, but that only works if the target element already exists. From #/playground, #/compose, or #/migration, the browser processes #why before Home is mounted, misses the anchor, and this hook never retries after route flips back to home, so the user lands on the wrong spot.
Suggested fix
export function useHashRoute(): Route {
const [route, setRoute] = useState<Route>(() => parse(window.location.hash))
+ const [hash, setHash] = useState(() => window.location.hash)
useEffect(() => {
- const onChange = () => setRoute(parse(window.location.hash))
+ const onChange = () => {
+ const nextHash = window.location.hash
+ setHash(nextHash)
+ setRoute(parse(nextHash))
+ }
window.addEventListener('hashchange', onChange)
return () => window.removeEventListener('hashchange', onChange)
}, [])
useEffect(() => {
// deep-dive pages and the explicit "`#/`" home link start at the top;
// bare "`#section`" hashes keep native anchor behaviour.
if (route.kind === 'page' || window.location.hash === '`#/`') {
window.scrollTo({ top: 0, behavior: 'instant' })
+ return
+ }
+
+ if (route.kind === 'home' && hash.startsWith('#') && !hash.startsWith('`#/`')) {
+ document.getElementById(hash.slice(1))?.scrollIntoView()
}
- }, [route])
+ }, [route, hash])
return route
}📝 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.
| /** | |
| * hash routing with two namespaces: `#/...` paths are routes (deep-dive | |
| * pages); bare `#section-id` hashes stay native anchor scrolls on the home | |
| * page. | |
| */ | |
| export function useHashRoute(): Route { | |
| const [route, setRoute] = useState<Route>(() => parse(window.location.hash)) | |
| useEffect(() => { | |
| const onChange = () => setRoute(parse(window.location.hash)) | |
| window.addEventListener('hashchange', onChange) | |
| return () => window.removeEventListener('hashchange', onChange) | |
| }, []) | |
| useEffect(() => { | |
| // deep-dive pages and the explicit "#/" home link start at the top; | |
| // bare "#section" hashes keep native anchor behaviour. | |
| if (route.kind === 'page' || window.location.hash === '#/') { | |
| window.scrollTo({ top: 0, behavior: 'instant' }) | |
| } | |
| }, [route]) | |
| /** | |
| * hash routing with two namespaces: `#/...` paths are routes (deep-dive | |
| * pages); bare `#section-id` hashes stay native anchor scrolls on the home | |
| * page. | |
| */ | |
| export function useHashRoute(): Route { | |
| const [route, setRoute] = useState<Route>(() => parse(window.location.hash)) | |
| const [hash, setHash] = useState(() => window.location.hash) | |
| useEffect(() => { | |
| const onChange = () => { | |
| const nextHash = window.location.hash | |
| setHash(nextHash) | |
| setRoute(parse(nextHash)) | |
| } | |
| window.addEventListener('hashchange', onChange) | |
| return () => window.removeEventListener('hashchange', onChange) | |
| }, []) | |
| useEffect(() => { | |
| // deep-dive pages and the explicit "`#/`" home link start at the top; | |
| // bare "`#section`" hashes keep native anchor behaviour. | |
| if (route.kind === 'page' || window.location.hash === '`#/`') { | |
| window.scrollTo({ top: 0, behavior: 'instant' }) | |
| return | |
| } | |
| if (route.kind === 'home' && hash.startsWith('#') && !hash.startsWith('`#/`')) { | |
| document.getElementById(hash.slice(1))?.scrollIntoView() | |
| } | |
| }, [route, hash]) |
🤖 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 `@tech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.ts` around
lines 17 - 37, The bare hash anchor handling in useHashRoute is missing a retry
after navigating back from a deep-dive page, so native scrolling can fire before
the Home content is mounted. Update the useHashRoute logic so that when
parse(window.location.hash) resolves to home with a bare section hash, the hook
performs the anchor scroll only after the home view is rendered, using the
existing route state and the hashchange-driven setRoute flow to trigger a second
attempt if needed. Keep the current special handling for Route.kind === 'page'
and '`#/`' intact, but make sure bare section hashes reliably scroll to their
target when leaving pages like playground, compose, or migration.
| @import "tailwindcss"; | ||
|
|
||
| @theme { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Stylelint config files =="
fd -HI '(.stylelintrc.*|stylelint.config.*|package.json)$' .
echo
echo "== Rules touching unknown at-rules or Tailwind =="
rg -n -C2 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|tailwind' \
$(fd -HI '(.stylelintrc.*|stylelint.config.*|package.json)$' .)Repository: iii-hq/iii
Length of output: 3911
Update Stylelint configuration to recognize Tailwind v4 at-rules.
The project explicitly depends on Tailwind v4 (tailwindcss@^4.3.0, @tailwindcss/vite@^4.3.0), but no Stylelint configuration file (.stylelintrc or stylelint.config) was found to whitelist the new at-rules. Consequently, @theme and @utility trigger scss/at-rule-no-unknown errors. Create a Stylelint config ignoring these specific rules or install a Tailwind-compatible linter plugin.
Diff context
`@import` "tailwindcss";
`@theme` {🧰 Tools
🪛 Stylelint (17.13.0)
[error] 3-3: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🤖 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 `@tech-specs/2026-06-devexp/presentation/src/index.css` around lines 1 - 3, The
Stylelint setup does not recognize Tailwind v4 at-rules, so add or update the
Stylelint configuration to whitelist Tailwind-specific directives used by the
CSS entrypoint. Make the change in the project’s Stylelint config (such as
.stylelintrc or stylelint.config) so that `@theme` and `@utility` are ignored by
scss/at-rule-no-unknown, or wire in a Tailwind-compatible Stylelint plugin that
covers these rules. Reference the CSS entrypoint that uses `@import` "tailwindcss"
and `@theme` to verify the config fixes the lint errors.
Source: Linters/SAST tools
| <StatusPanel | ||
| variant="success" | ||
| headline="phase 2 is already shipped in part" | ||
| detail="all seven built-in workers self-register their schema + initial value at boot via configuration::register and read the configuration store — there is no seed and no config: block to migrate. configuration is owned end-to-end by the configuration worker." |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Don't describe config migration as manual re-entry.
This copy says there is “no seed” / “no config: block to migrate” and tells operators to re-apply tuned values with iii worker config set. The established contract is that worker config.yaml blocks are seed-only and, once persisted, are stripped with a breadcrumb to ./data/configuration/<id>.yaml. As written, this changes the migration semantics and suggests previously tuned values are discarded. As per path instructions, "config.yaml blocks for workers are seed-only and should be stripped once their value is persisted; on next boot, keep the - name: entry but replace the dead config: block with a breadcrumb pointing to ./data/configuration/<id>.yaml."
Also applies to: 75-76
🤖 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 `@tech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsx` around
lines 58 - 61, The MigrationPage copy is incorrectly describing worker
configuration migration as if there is no seed or config block and as if
operators must re-enter values manually. Update the StatusPanel and related
migration text in MigrationPage to reflect the established contract: worker
config.yaml blocks are seed-only, persisted values are stripped from the seed,
and the remaining worker entry should keep the - name: line with a breadcrumb to
./data/configuration/<id>.yaml instead of suggesting manual re-entry or
discarded tuned values.
Source: Path instructions
| // --no-frozen-lockfile so a slightly-stale committed lockfile self-heals | ||
| // instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise | ||
| // defaults to frozen, which aborts on any lockfile/package.json drift — a | ||
| // confusing "builds locally, fails on Vercel" trap for future decks. | ||
| if (!SKIP_INSTALL) | ||
| run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Don't let deploys resolve an unreviewed dependency graph.
--no-frozen-lockfile makes the Vercel build silently regenerate dependency resolution when a manifest drifts, so production can ship packages that were never committed or reviewed. Keep frozen installs for CI/deploys and only allow the self-healing path behind an explicit local-only opt-in if you still want that escape hatch.
Suggested change
function buildProject(dir, label) {
console.log(`\n▸ ${label}`)
- // --no-frozen-lockfile so a slightly-stale committed lockfile self-heals
- // instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise
- // defaults to frozen, which aborts on any lockfile/package.json drift — a
- // confusing "builds locally, fails on Vercel" trap for future decks.
- if (!SKIP_INSTALL)
- run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir)
+ if (!SKIP_INSTALL) {
+ const installArgs = ['install', '--ignore-workspace']
+ if (!process.env.CI) installArgs.push('--no-frozen-lockfile')
+ run('pnpm', installArgs, dir)
+ }
run('pnpm', ['build'], dir)
}As per path instructions, "Check all PRs for security issues and intentional supply chain compromises / malicious code."
📝 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.
| // --no-frozen-lockfile so a slightly-stale committed lockfile self-heals | |
| // instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise | |
| // defaults to frozen, which aborts on any lockfile/package.json drift — a | |
| // confusing "builds locally, fails on Vercel" trap for future decks. | |
| if (!SKIP_INSTALL) | |
| run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir) | |
| if (!SKIP_INSTALL) { | |
| const installArgs = ['install', '--ignore-workspace'] | |
| if (!process.env.CI) installArgs.push('--no-frozen-lockfile') | |
| run('pnpm', installArgs, dir) | |
| } |
🤖 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 `@tech-specs/build.mjs` around lines 53 - 58, The install step in build should
not silently refresh the dependency graph during deploys; `run` in the
`build.mjs` flow is currently passing `--no-frozen-lockfile`, which can let
CI/Vercel regenerate lockfile resolution from an unreviewed manifest drift.
Update the install path to use frozen lockfile behavior by default for
deploy/CI, and if you keep a self-healing escape hatch, gate it behind an
explicit local-only opt-in around the existing `SKIP_INSTALL` / install logic so
production builds never resolve dependencies implicitly.
Source: Path instructions
c60c1a4 to
5a34eb0
Compare
| │ ├── start <W>... [--no-wait] # runtime → process-daemon | ||
| │ ├── stop <W>... [-y|--yes] [-t|--timeout <s>] | ||
| │ ├── restart <W>... [--no-wait] |
There was a problem hiding this comment.
let's remove it, compose up/down/restart already manipulates individual workers
There was a problem hiding this comment.
let's think about this regarding sandbox
There was a problem hiding this comment.
let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::
| │ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>] | ||
| │ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI |
There was a problem hiding this comment.
let's remove it, compose status/logs already pulls from individual workers
There was a problem hiding this comment.
let's think about this regarding sandbox
There was a problem hiding this comment.
let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::
| │ ├── restart <W>... [--no-wait] | ||
| │ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>] | ||
| │ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI | ||
| │ ├── ps [--json] # NEW: process table across managed procs |
There was a problem hiding this comment.
let's remove it, compose ps already pulls from individual workers
There was a problem hiding this comment.
let's think about this regarding sandbox
There was a problem hiding this comment.
let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::
| │ ├── add <SRC>... [-f|--force] [--up] [--no-wait] | ||
| │ │ # SRC = name[@ver] | oci-ref | ./path ; edits compose+lock, does NOT auto-start | ||
| │ ├── update [W]... [-f|--force] [--clear-artifacts] [--restart] [--no-wait] | ||
| │ ├── remove <W>... [--clear-artifacts] [-y|--yes] |
There was a problem hiding this comment.
let's think about this
| │ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>] | ||
| │ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI | ||
| │ ├── ps [--json] # NEW: process table across managed procs | ||
| │ ├── exec <W> [-e K=V] [-w CWD] [-t|--tty] [--timeout <ms>] -- CMD... |
There was a problem hiding this comment.
let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::
| │ ├── start <W>... [--no-wait] # runtime → process-daemon | ||
| │ ├── stop <W>... [-y|--yes] [-t|--timeout <s>] |
There was a problem hiding this comment.
let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::
Summary by CodeRabbit