Repository navigation
fix(fields): a file URL whose last segment holds a bare % renders, named by its raw segment (objectui#10614) - #10653
Conversation
…named by its raw segment `nameFromUrl` passed a URL's last path segment to `decodeURIComponent` unguarded, so a segment that is not a valid percent-encoding (`https://cdn.example.com/100%.png`, `{ url: 'https://cdn.example.com/a%zz.pdf' }`) threw `URIError: URI malformed` out of `readFileValue` during render. The decode is now guarded once, in that helper: on `URIError` the name is the raw segment. A valid escape (`report%20q3.pdf`) decodes as before. Pins: both reported values at the helper and through the `file` cell renderer, plus the valid-escape control. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…ting it Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…le-name-bare-percent
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Read-only review of #10653 (card objectui#10614) against ① Derived judgmentsBinding text. Triage
② Semver level
Changeset prose, sentence by sentence against the diff:
Docs/README prose added by the diff: none under ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10614
Clause-②: no
What changed
nameFromUrlinpackages/fields/src/widgets/file-value.tsnames a file value that carries no name after its URL's last path segment, decoded withdecodeURIComponent. A segment that is not a valid percent-encoding made the decode throwURIError: URI malformed, and the throw leftreadFileValueduring render. The decode is now guarded once, in that helper. OnURIErrorthe name is the raw segment; any other error is rethrown. No caller changed, and no caller has its own try/catch (triage ruling on the card, comment5833345168). A valid escape decodes exactly as before.Files:
packages/fields/src/widgets/file-value.ts(the guard and its doc comment),packages/fields/src/widgets/file-value.test.ts(helper pins),packages/fields/src/widgets/file-value.bare-percent-10614.test.tsx(rendered-face pins, new),.changeset/10614-file-name-bare-percent.md(patch,@object-ui/fields).Reach, measured first at the base
eca2760A scratch harness (not committed) mounted every face that reaches the helper. Values:
https://cdn.example.com/100%.png(string) and{ url: 'https://cdn.example.com/a%zz.pdf' }(object).filecell (FileCellRenderer), string and arrayURIError: URI malformed100%.png/a%zz.pdfimagecell (ImageCellRenderer), string and objectURIError: URI malformedsignaturecell (viagetCellRenderer)URIError: URI malformedFileFieldedit and readonlyURIError: URI malformedFileCellURIError: URI malformedImageFieldedit and readonlyURIError: URI malformedobject-gridthroughSchemaRenderer, one bad row plus one valid rowdata-tablenode's error boundary replaces the WHOLE table with its panelComponent "data-table" failed to render/URI malformed/Retry. Neither row is visible.object-gallerythroughSchemaRenderer, one bad cover plus one validComponent "object-gallery" failed to render/URI malformed/Retryfilecell withreport%20q3.pdfreport q3.pdfreport q3.pdfSo at the base, no face contained the throw. Under
SchemaRenderer, the nearest error boundary caught it, and one such value hid the whole table or gallery, valid rows included. The measurement did not produce a blank page, becauseSchemaRendererwraps every node in a boundary. The console's list route reaches the grid throughListView, which renders it throughSchemaRenderer.ObjectGallery's cover (resolveCoverUrlcallsreadFileValues) is a caller the card did not list.git grepfound it, and it was measured above.Pins, and the ablation
file-value.test.ts, the describe block whose name endsnames the file by its raw segment (objectui#10614). It pins both reported values (no throw, name is the raw segment), the two together throughreadFileValues, and THE CONTROL:report%20q3.pdfstill decodes toreport q3.pdf, both as a string and as{ url }.file-value.bare-percent-10614.test.tsxrenders thefilecell throughgetCellRenderer('file'). It checks both values as links named by the raw segment with the righthref, a single non-array value, and the control decoding beside a bare-%value in the same cell.551e8edwithablation-replace.mjs(wrap mode, anchorif (err instanceof URIError) return seg;deleted, so the throw escapes again as it did at the base). It printedanchor x1 -> x0and bloba2c261c8e7d4 -> e873660a8615. The two pin files then readTests 6 failed | 39 passed (45),command exited 1, and every failure isURIError: URI malformed. The helper control stays green in that run, as a control should. Restore:blob == HEAD (a2c261c8e7d4) and git diff HEAD is empty. Direction observed: red, as expected.Gates, at head
1980ada(after mergingorigin/mainf99f9cd)pnpm exec vitest run packages/fields/: exit 0,Test Files 209 passed | 1 skipped (210),Tests 3399 passed | 7 skipped (3406)pnpm turbo run type-check --filter @object-ui/fields --concurrency=2: exit 0,Tasks: 12 successful, 12 total, and@object-ui/fields:type-check: cache miss, executing, so it ran rather than replaying a cache.tsc -p tsconfig.test.json --listFileslists both pin files, so the test program covers them.pnpm exec eslinton the three changed source and test files: exit 0, no problemspnpm check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations(0 new),check:changeset-claims,check:pending-changeset-literals,check:unreferenced-sources: exit 0 eachnode scripts/check-changeset-presence.mjs,check-changeset-no-major.mjs,check-changeset-overwrite.mjs,check-lint-coverage.mjs: exit 0 eachpnpm lintand the full test shards belong to CI.Acceptance notes
SchemaErrorBoundarysits at the schema node, so any throw from one cell renderer blanks the wholedata-tablenode, not the cell. This card removes the throw. It does not change where boundaries sit.hrefis the value's own URL, unchanged.Generated by Claude Code