Skip to content

feat(screen-plan): follow the final design page; audit treats headless-hidden scrollbars as visible - #110

Draft
devfive-relay-c70423[bot] wants to merge 162 commits into
mainfrom
devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames
Draft

devfive-relay-c70423[bot] wants to merge 162 commits into
mainfrom
devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames

Conversation

@devfive-relay-c70423

@devfive-relay-c70423 devfive-relay-c70423 Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Screen plan follows the final design page. State-variant frames (Desktop 1441, Mobile 361/362, extra form states) now fold into the route they belong to instead of becoming new screens. remap moves an existing route to a newer frame.
  • Runtime audit: headless-hidden scrollbars. Headless Chromium hides every scrollbar (--hide-scrollbars). Because of that, no-clipped-content reported plain overflow-x: auto tab strips and tables as clipped content. The runner now probes whether the browser hides scrollbars, and in that case only scrollbar-width: none counts as an invisible scroller. Covered by a new case in test/layout.test.mjs.
  • Feedback batch (items 2-66), from building chondrogen. One commit per item, merged from six branches:
Area Items What changed
Codegen 66, 64, 65, 44, 32 componentTsx works when the root is a component set, a single variant or an instance, and is named after the set. data-figma-node-id is only emitted with figmaNodeIds: true. Instances resolve to their component. Link hover/focus states sit on the element rendered as the link.
Search / bridge 8, 13, 50, 57, 35 Search lists by node type with an empty query, scans every page without switching, and reports when results were cut. A bad limit is an input error. Trash/hidden frames are flagged. Bridge referencePng accepts a single frame id and reports PNGs it can't render instead of dropping them.
Project plan 2, 7, 49, 51, 52, 54, 55 confirm no longer overwrites model files and drops columns that exist on disk (55). status without planId returns recovery actions. intake on an existing plan offers resume or an archived restart. Column type errors and model drift are reported. Recommendations follow recorded evidence.
Screen plan 14, 15, 53, 56, 58, 59 The proposal hides inactive conditional rows and groups collision rows. Site metadata is checked in the app the plan targets. auditPlan run from apps/front finds the plan at the monorepo root, and a rerun refreshes its own output. 14 and 15 were already fixed; regression tests added.
Runtime audit 60, 61, 62, 63, 46 The emitted runner includes every module it imports. A page without document.body or a failing probe makes that one check inconclusive instead of aborting the run. Checks anchored on node ids absent from the source are not-run with a reason. auditPlan returns the exact dev-server start command (the runner does not start it itself). 46 was already fixed.
Skills / docs 42, 48, 64, 65 User decisions go through the question tool. New figma-bridge-setup skill. No instruction to add an audit plugin to the app build config. Component-first workflow: export components with componentTsx, record .devup/component-map.json, then assemble pages; no hand-written TSX from Figma nodes.

Not done in this PR: the runner starting the dev server itself (60), a devup_figma_bridge ensure tool and stale-host/out-of-view next actions in status (48), section-level export (65), and the legacy strip-plugin file still emitted by auditPlan (64).

Follow-up commits

Commit What changed
feat(bridge) The plugin reports its scripts hash and protocol. Status and exports flag a stale plugin (pluginStale, DEVUP_BRIDGE_PLUGIN_STALE). Live script updates: a plugin that sends liveScripts: true and speaks the same protocol is sent this build's scripts over the bridge socket (scripts-update). It swaps them in and says hello again with the new hash, so a rebuilt server reaches an open plugin without closing and re-running it. A bundle that fails to load leaves the current scripts in place. Plugins built before this change need one manual re-run.
feat(ui-validate) Effective attributes, summary findings and disk paths.
feat(screen-plan,audit) Component widths, responsive contract, dynamic URLs, settled scroll reveals.
docs(skills) Component-first workflow and guide updates; figma-bridge-setup checks status again before a manual rebuild.
feat(codegen,export) Instance override reporting, default code paths, component references.
fix(stack-diff,ui-validate) Found while reviewing chondrogen. stack_diff openapi-client and feature_trace only recognised calls on a receiver named api, so client.get('/posts') through export const client = createApi(...) gave callsChecked: 0. They now collect createApi bindings across the project and follow them through imports and aliases. The hardcoded-length suggestion no longer says "colors". bridge/stamp_file_key.rs is split (fallback_reasons.rs) to stay under the 1000-line source limit.
fix(ui-validate) 35b0aff Found running devup_ui_validate paths on chondrogen. Summary mode now groups hardcoded-length/hardcoded-color findings by value and token across the whole project, with count, props and files[].lines (chondrogen hit the 100-row cap with the same gap 8px row per file). 0px lengths are not reported. A token named for another purpose ($spacing24 for a radius, a stroke-width token for a height) is info with no replacement advice; a same-purpose token is preferred. Mode advice is stated once. correctedTsx only uses the tokens a finding suggested.
fix(ui-validate) 2d72c83 Summary rows are ordered error, warning, info before the 100-row cap, so info rows can no longer push out errors; violationsTruncated.hiddenBySeverity counts what was cut. Layout risks point at the element they sit on.
fix(ui-validate) 1ee3d8f Found comparing chondrogen's Figma with the validator's advice. A hardcoded value matched a theme token by value, but the Figma node had either bound a different variable or no variable at all. With the export's sourceMap, each hardcoded-* finding now has designBinding: bound (with variableIds) means use the token generated for that variable, not one that just shares the value; unbound means the design hardcodes it too, so tokenMatches are a coincidence of values. SourceMapEntryInput accepts variableId, and summary rows keep and group by the binding state. Test: hardcoded_values_report_the_figma_binding_behind_them (full and summary, bound and unbound).
fix(ui-validate) d6617b3 Follow-up to 1ee3d8f. A hardcoded-color/hardcoded-length warning whose Figma node binds no variable now drops to info and drops the token replacement advice; the summary message says the design hardcodes it too. This runs before severities are counted, so ok, okReason, strict and correctedTsx agree. Bound values stay warnings. Without a sourceMap nothing changes. Test: an_unbound_value_matching_a_token_is_info (strict, full and summary, bound and unbound).
fix(ui-validate) bf9a149 Each hardcoded-length finding carries lengthKind (radius, stroke, space, size, text, other), and summary rows group by it. A 12px borderRadius no longer shares a row, and its token message, with 12px gap. Test: summary_rows_split_lengths_by_kind.

Needs a maintainer: the push token cannot change workflow files, so .github/workflows/ci.yml does not yet run the new plugin/tests/live-scripts.test.mjs. Add it to the node --test line after plugin/tests/withdraw.test.mjs.

Live-script checks: cargo test -p devup-mcp-figma (including tests/live_scripts.rs and the bundle fingerprint), devup-mcp skills/diagnostics/bridge_first/bridge_relay tests, clippy -D warnings, fmt --check, plugin npm run build/verify, tsc --noEmit, and node --test (11 pass) all pass.

Verification

Check Result
cargo fmt --all --check pass
cargo clippy --workspace --all-targets -- -D warnings pass
cargo test --workspace pass (145 suites, 0 failed)*
harness/runtime-audit npm test pass
1ee3d8f: cargo clippy -p devup-mcp --all-targets -- -D warnings, cargo test -p devup-mcp --test ui_validate_response pass (79 tests)
d6617b3: same clippy, fmt --check, cargo test -p devup-mcp --test ui_validate_response pass (80 tests)
bf9a149: clippy -D warnings on devup-mcp and devup-mcp-devup-ui, fmt --check, ui_validate_response (81) and cargo test -p devup-mcp-devup-ui pass

* r3_asset_job_timeout_keeps_first_asset_and_resumes_pending_read failed once on the first full run and passed alone (3/3) and on the next two full runs. It uses a paused clock and looks timing-sensitive under load.

Tested on chondrogen (14 routes, 5 widths: 1440/1100/858/390/360)

Earlier in this PR, before the feedback batch. Plans were regenerated with devup_screen_plan auditPlan and run with the updated runner.

Route Before the runner fix After
/ 58 pass / 5 fail 63 pass / 0 fail*
/company 63 / 0 63 / 0
/company/vision 63 / 0 63 / 0
/company/location 63 / 0 63 / 0
/technology 61 / 2 63 / 0
/technology/research 61 / 2 63 / 0
/technology/osteoarthritis 61 / 2 63 / 0
/technology/patents 61 / 2 63 / 0
/technology/achievements 61 / 2 63 / 0
/products 57 / 1 / 5 inconclusive 58 / 0 / 5 inconclusive
/products/oa-model 62 / 1 63 / 0
/news 63 / 0 63 / 0
/news/notice 63 / 0 63 / 0
/contact 63 / 0 63 / 0

* The 5 failures on / were a real finding: a decorative marquee in a 1.22:1 colour. It was fixed in the app (aria-hidden), not in the runner.

Remaining: /products has 5 no-low-contrast-text results that are inconclusive, where 2 text samples have backgrounds that can't be judged. /news/[id] is not audited because no static params or detail-frame text are available.

chondrogen app checks after the changes: bun run lint:fix, oxlint --deny-warnings, tsc --noEmit, bun run build, bun test (4 pass, 100% coverage) all exit 0.


Submitted via Devfive hub. Employee: ; device: 3099154426bffbeef8b62999b80bb038

leader added 30 commits October 22, 2026 17:15
…s-hidden scrollbars as visible

State-variant frames (Desktop 1441, Mobile 361/362, extra form states) fold into their route instead of becoming new screens, and remap moves a route to a newer frame. The runtime audit no longer reports overflow:auto tab strips and tables as clipped just because headless Chromium hides every scrollbar; only scrollbar-width:none still counts.
Existing envelope spill fixes cover the reported single-frame scenario. Assume synthetic 569-issue metadata reproduces its transport pressure without retaining private design data.
Reuse existing narrowed recovery and spill behavior. Assume top eight serialized JSON fields are the useful diagnostic budget; preserve total retained output bytes when checking the resource envelope.
Reuse one stem for identical image hashes across layer roles and preserve variant identity. Refresh codegen snapshots and fixture checksums for kebab-case asset paths; refresh platform float serialization in the diagnostics snapshot.
Keep existing manifest auto-add behavior, disclose it, select job fields after job attachment, and judge requested captures rather than all discovered assets. Assume nodes paths remain capture-scoped while other paths prefer the assembled reply. Exercise MCP calls and actual output file bytes in regression coverage.
…s (30)

Existing original-source defaults already choose WebP q85. Apply the display-width times two cap when omitted and auto-select lossless encoding for alpha graphics with at most 256 colors. Explicit codec and lossless choices remain authoritative.
Keep the existing shared manifest-to-TSX reconciliation, verified by its real-file dedup regression. Add required placeholder paths to reviewChecklist even when acquisition itself is complete.
Resolve node requests to captured fill evidence before determining write state, and avoid reporting a request's own internal fill as a duplicate. Existing partial/resume and request-count regressions remain covered.
Assume legacy plans without app fields remain valid; explicit route app bindings must match. Search all ancestors rather than four levels.
…es (19)

Assume existing section-based device renaming is authoritative; reproduce Main Desktop/Mobile alongside another section without a mobile URL.
leader added 30 commits October 24, 2026 02:46
…nto devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames
…devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames
…o devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames
…o devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames
…nto devfive/3099154426bffbeef8b62999b80bb038/screen-plan-final-frames

# Conflicts:
#	crates/devup-mcp/src/server/text/devup-project-plan.md
- quote non-identifier lookup keys (button set produced invalid TSX)
- treat an aborted OXC parse as a validation error (parserAborted)
- expose unbound TEXT nodes in a component set as string props with the Figma copy as default
- drop set-canvas placement from variant roots, keep a containing block for absolute descendants
- emit cond ? value : undefined instead of cond && value
…ext prop names

- account for diagnosed variant-child fallback nodes as ignored trace entries so componentTsx no longer fails the exactly-once check (ApplicationsCard, RnDLinkRow, TechStep)
- give non-ASCII declared TEXT properties unique identifiers instead of overwriting defaults
…ops, partial variant maps, boolean props and state selectors

- keep typography and Image src scalar across responsive variants
- add undefined branches for variant lookups missing union keys
- declare and destructure referenced boolean props with collision-free names
- map selected/hoevr states to valid selectors; keep src out of pseudo objects
…src off container variant merges

- one-sided responsive Image src becomes scalar src plus endpoint visibility
- source splitting only applies to Image; container merges never take a leaf variant's src
- guard test across all chondrogen fixtures: no scalar arrays, no src on non-Image elements
…nd keep boolean-controlled layers

- variant lookups use Definition.source, so icon/mask/src/state styles are no longer dropped (empty Caret/Logo/DropdownOption bodies)
- children with a visible property reference count as visible for asset classification (IconButton text layer)
- responsive expressions drop redundant trailing inheritance and collapse slot-0-only arrays to scalars
- guard test: every declared prop is referenced in the body
…ps href, and treat controls inside links as wired
…primitive as a dynamic target, not unmapped
…itory root holds from an app directory, and status never takes another app's page for a route
…nparsed files, not truncated component lists
…ly variant of a text component set stays a component reference
…ces a vector-only variant when another width's instance of the set has text
The plugin reports its scripts hash and protocol; status and exports flag a stale plugin. A plugin that reports liveScripts and speaks the same protocol is sent this build's scripts over the bridge socket (scripts-update), swaps them in and says hello again with the new hash, so a rebuilt server reaches an open plugin without closing and re-running it.
…ame the token kind, keep bridge source under the line limit

stack_diff openapi-client and feature_trace recognised only a receiver named api, so a project calling client.get('/posts') through 'export const client = createApi(...)' reported callsChecked 0. Bindings to createApi are now collected project-wide and followed through imports and aliases. The hardcoded-length suggestion no longer says colors. bridge/stamp_file_key.rs was split (fallback_reasons.rs) to stay under the 1000-line source limit.
…ths, prefer purpose-matching tokens

- summary mode groups hardcoded-length/color findings by (rule, severity, value, tokens) across files, with count, props and files[].lines
- 0px lengths are never reported
- tokens named for another purpose (radius/stroke/space/size/text) are info without replacement advice; same-purpose tokens are preferred
- mode advice is stated once (suggestion + tokenMatches), findings carry value/prop context
- corrected_tsx only considers the tokens the finding actually suggested
…ut risks point at their element

- summary rows are ordered error, warning, info before the 100-row cap; violationsTruncated reports hiddenBySeverity
- column-reading-order, narrow-fixed-columns and navigation-fit carry the byte offset of the element they judge, so line/column are real instead of 1
…d value

A sourceMap entry's variableId now reaches the violation as designBinding: bound means use the token generated for that variable rather than one that merely shares the value; unbound means the design hardcodes it too. Summary rows keep it and group by it.
…s info

With the export's sourceMap, a hardcoded-color/hardcoded-length warning whose Figma node binds no variable drops to info with no token replacement: a theme token that only shares the value is a coincidence. Runs before severities are counted, so ok, okReason, strict and correctedTsx agree. Bound values stay warnings.
…easure

Each hardcoded-length finding carries lengthKind (radius, stroke, space, size, text, other) and summary rows group by it, so a 12px radius no longer shares a row (and its token message) with 12px spacing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants