penpot_add_text stages zero-area auto-width text, producing files that fail update-file with NaN points #252
Closed
opened 2026-09-17 03:06:13 +00:00 by david
·
1 comment
No Branch/Tag specified
main
feature/issue-264/verify-the-vision-tool-live-against-the-deepseek-a
feature/issue-263/list-the-vision-extension-in-the-repo-root-readme
feature/issue-262/write-extensions-vision-readme-md-the-10-section-p
feature/issue-261/wire-the-vision-extension-into-package-json-manife
feature/issue-260/implement-extensions-vision-index-ts-factory-regis
feature/issue-258/implement-extensions-vision-src-tool-ts-the-vision
feature/issue-257/implement-extensions-vision-src-client-ts-request
feature/issue-256/implement-extensions-vision-src-usage-ts-usage-map
feature/issue-255/implement-extensions-vision-src-images-ts-read-val
feature/issue-254/implement-extensions-vision-src-config-ts-env-reso
feature/issue-253/implement-extensions-vision-src-errors-ts-toolerro
feature/issue-252/penpot-add-text-stages-zero-area-auto-width-text-p
feature/issue-216/add-the-penpot-skill-and-extension-to-the-repo-roo
feature/issue-217/register-the-penpot-tests-in-npm-test-complete-the
feature/issue-215/write-the-penpot-skill-md-core-workflow-token-conv
feature/issue-214/implement-and-run-fetch-penpot-docs-mjs-and-commit
feature/issue-213/validate-screenshot-reconstruction-against-the-liv
feature/issue-212/add-the-image-to-design-workflow-to-skills-penpot
feature/issue-211/validate-svg-import-icon-and-illustration-against
feature/issue-210/implement-the-penpot-add-svg-tool-with-svg-raw-fal
feature/issue-209/implement-the-svg-to-shapes-converter-in-src-svg-t
feature/issue-208/validate-component-instancing-against-the-live-ins
feature/issue-207/implement-penpot-instance-component-with-id-remapp
feature/issue-206/author-and-commit-the-component-library-artifact-u
feature/issue-205/implement-file-library-linking-and-validate-the-ex
feature/issue-204/implement-penpot-export-library-sse-uri-plus-artif
feature/issue-203/implement-penpot-import-library-multipart-upload-p
feature/issue-202/implement-the-transit-decoder-and-sse-stream-parse
feature/issue-201/validate-a-full-composed-screen-one-commit-one-rev
feature/issue-200/implement-penpot-add-image-with-media-upload
feature/issue-199/implement-asset-reference-resolution-plus-penpot-a
feature/issue-198/implement-penpot-add-frame-with-auto-layout-props
feature/issue-197/validate-asset-creation-atomicity-and-write-safety
feature/issue-196/implement-the-designated-target-write-guard-with-a
feature/issue-195/implement-penpot-commit-with-revn-tracking-conflic
feature/issue-194/implement-the-staged-changeset-store-and-the-colou
feature/issue-193/validate-read-primitives-against-the-live-penpot-i
feature/issue-192/implement-the-penpot-list-library-tool-with-name-t
feature/issue-191/implement-the-penpot-get-file-tool-pages-objects-a
feature/issue-190/implement-the-penpot-list-projects-tool-teams-and
feature/issue-189/implement-the-penpot-whoami-tool-and-wire-the-exte
feature/issue-188/implement-the-rpc-client-and-error-decoding-in-src
feature/issue-187/implement-penpot-url-penpot-token-resolution-in-sr
feature/issue-186/scaffold-the-penpot-extension-directory-and-regist
feature/issue-172/add-end-to-end-main-tests-for-the-fj-rg-tool-state
feature/issue-171/make-reminder-report-dynamic-install-skip-counts-r
feature/issue-170/wire-settings-json-filter-rewrite-into-dedupeandre
feature/issue-169/implement-buildpackagefilters-with-exhaustive-unit
feature/issue-168/refactor-preflight-to-skip-warn-for-missing-fj-rg
feature/issue-167/add-fj-rg-tool-detection-helpers-rgavailable-detec
feature/issue-148/reconcile-design-md-and-implementation-plan-with-t
feature/issue-146/verify-extension-load-behavior-with-and-without-mo
feature/issue-145/write-extensions-mongodb-readme-md
feature/issue-144/write-tool-description-promptsnippet-guidance-for
feature/issue-143/implement-index-ts-async-factory-lifecycle-with-te
feature/issue-142/implement-the-mongo-list-collections-tool-with-uni
feature/issue-141/implement-the-mongo-count-tool-with-unit-tests
feature/issue-140/implement-the-mongo-find-tool-with-unit-tests
feature/issue-139/implement-src-errors-ts-with-unit-tests-toolerror
feature/issue-138/implement-src-serialize-ts-with-unit-tests-ejson-t
feature/issue-136/implement-src-env-ts-with-unit-tests-mongodb-uri-r
feature/issue-135/register-the-mongodb-extension-in-the-repo-root-pa
feature/issue-133/initialize-the-extensions-mongodb-package-package
feature/issue-116/src-actions-ts-downloadrunlogs-zip-guarded-unzip-c
feature/issue-115/src-actions-ts-downloadjoblog-cache-first-attempt
feature/issue-114/src-actions-ts-metadata-queries-listactionruns-get
feature/issue-113/src-actionscache-ts-cache-paths-cache-first-read-w
feature/issue-112/add-zip-extraction-dependency-adm-zip-vs-yauzl
feature/issue-111/tracking-forgejo-actions-tooling-read-only-runs-jo
feature/issue-93/add-unit-tests-for-scripts-local-install-mjs-node
feature/issue-92/validate-install-local-end-to-end-on-the-dev-machi
feature/issue-90/implement-pi-registration-with-url-dedupe-and-relo
feature/issue-89/validate-install-local-from-a-scratch-clone-fresh
feature/issue-88/implement-git-pull-and-npm-install-steps-in-script
feature/issue-74/write-victorialogs-readme-md-and-reconcile-design
feature/issue-73/wire-up-index-ts-extension-factory-and-register-al
feature/issue-72/implement-logs-facets-tool-src-tools-facets-ts
feature/issue-71/implement-logs-hits-tool-src-tools-hits-ts
feature/issue-70/implement-logs-search-tool-src-tools-search-ts
feature/issue-69/implement-parsejsonlines-helper-in-src-client-ts
feature/issue-68/implement-victorialogsrequest-in-src-client-ts
feature/issue-67/implement-resolvebaseurl-in-src-env-ts
feature/issue-66/implement-totoolerror-in-src-errors-ts
feature/issue-65/implement-src-defaults-ts-shared-constants-and-app
feature/issue-64/scaffold-the-victorialogs-pi-extension-project
feature/issue-53/skip-issue-creation-for-chore-documentation-commit
feature/issue-45/fix-local-install-preflight-env-var-requirements-s
feature/issue-41/add-npm-run-local-install-script-to-update-and-ins
feature/issue-26/cross-reference-forgejo-list-milestones-from-issue
feature/issue-25/add-forgejo-milestone-delete-tool
feature/issue-24/add-forgejo-milestone-close-and-forgejo-milestone
feature/issue-23/add-forgejo-milestone-edit-tool
feature/issue-22/add-forgejo-milestone-view-tool
feature/issue-21/add-forgejo-list-milestones-tool
feature/issue-20/add-forgejo-milestone-create-tool
feature/issue-18/add-forgejo-label-create-tool
feature/issue-29/bug-forgejo-issue-create-view-edit-close-etc-crash
No results found.
Labels
No labels
bug
chore
documentation
enhancement
feature
ready
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
david/pi-extensions-and-skills#252
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
penpot_add_textstages text shapes withwidth: 0/height: 0andgrowType: "auto-width"whenever the caller omits dimensions. Penpot performs no text layout server-side — the client computes it — so the degenerate geometry is committed and persisted as-is. The first client-side interaction with one of these shapes producesNaNpoints, the server's post-apply assertion rejects the change with HTTP 500, and the frontend keeps the failed change queued and retries on every load. The file then appears permanently broken in the editor and can only be recovered out-of-band via the backend nREPL.Confirmed against a real file on the live instance (Penpot 2.17.2,
penpot.excelera.net), captured in the debug bundlepenpot-bug-1e70e922-REPORT.md.Impact
POST /api/main/methods/update-filefail with 500, blocking all further authoring of that file.:type :text,:grow-type :auto-width,w0 h0). The page labels were correct; only geometry was missing.app.srepl.main/update-file!); there is no client-side remedy while the bad change is queued.extensions/penpot/findings.md:614-618: "No auto-measurement is performed, so geometry staged from the tools can differ from what the UI would compute." A regression test currently asserts the buggy behaviour.Evidence
From
penpot-bug-1e70e922-REPORT.md(file id1e70e922-ca4f-4d1c-a99a-59327eaf57e8, revn 10, 441 shapes, 150 zero-size):Stored shape (the shape that broke the save):
Server rejection:
Two further facts from the report that matter for the diagnosis:
validate-file→ 0 errors,validate-file-schema→ OK, 0 NaN anywhere). The failing client change carried:y nil/:width nilfor the:auto-widthtext, and the point computation turnednilintoNaN. Our tool commits the degenerate geometry that makes the client's next layout pass produce thatnil/NaN— the 500 is downstream of our output.Root cause
The staged payload is ours, and it matches the stored pathosis exactly.
extensions/penpot/src/tools/text.ts:301-310:extensions/penpot/src/shapes.ts:443-470then derives the collapsing geometry:pointsFrom(697, 97, 0, 0)→ all four corners are697,97(shapes.ts:395-406)selrectFrom(697, 97, 0, 0)→ zero-area selrect (shapes.ts:410-426)growType: "auto-width"is correct Penpot semantics if and only if a client layout pass will follow and write back a real box. For a stagedadd-objviaupdate-filethere is no such pass, so the zero persists. The report's own conclusion agrees: "the shapes were created without a completed text layout pass ... Two things worth fixing on the tool side: set/verify real geometry (or force a text re-layout) before/after adding text, and never send nil for:width/:height/:y."The second half of that recommendation is already satisfied — we emit numeric
0, nevernil. The first half is the defect.Affected code
Every text path routed through
baseShapeis affected, not justpenpot_add_text:penpot_add_textsrc/tools/text.ts:301-310width/heightstages0/0src/tools/svg.ts:663-670,:718growType: "fixed"but geometry comes from the SVG bounds, which can be zero for a text node with no explicit dimensionssrc/libraryArtifact.ts:506shape.growType = node.growType ?? "auto-width"; current spec nodes carry realwidth/height, but the default is the same footgunsrc/shapes.ts:395-426,:443-470pointsFrom/selrectFrom/baseShapehave no zero-area or finite-number invariantsrc/tools/commit.ts:456-528update-filePrecedent to follow
extensions/penpot/src/tools/image.ts:85-118already solves the same class of problem for images:Text should behave the same way, but it has no intrinsic size to fall back on — so it needs a local estimate (see below).
Proposed guards
1. Invariant in
baseShape(the choke point) — requiredAfter deriving
selrect/points, refuse the shape when:x,y,width,heightis non-finite (NaN,Infinity), for all shape types; andwidth <= 0 || height <= 0fortype: "text"(andtype: "image", which already guards upstream).This single check covers
penpot_add_text, SVG-imported text, and library-artifact text. Throw with an actionable message naming the shape and the missing dimension rather than staging a degenerate box.2. Don't let an omitted size become a stored zero (
text.ts) — requiredTreat a supplied
0as "not supplied" (matchingimage.ts'spositiveDimension), and when a dimension is genuinely omitted, compute a conservative estimate instead of0:height = line-height × font-size × paragraph-countwidth = longest-line character count × font-size × ~0.58, floored at roughly8 × 16Keep
growType: "auto-width"/"auto-height"so Penpot still recomputes on first client layout; the estimate only ensures the committed box is non-degenerate. This mirrors the heuristic that successfully repaired the real file (report §7).3. Pre-flight the staged batch in
penpot_commit— recommendedBetween the preview (
src/tools/commit.ts:456) and theupdate-filesend (:528), scan stagedadd-obj/mod-objshapes for text with zero/non-finite geometry and refuse with an actionable message, leaving the changeset staged. Defence in depth against any path not yet audited.4. Make the schema and tests honest — required
src/tools/text.tsschema:width/heightcurrently allowminimum: 0. Switch toexclusiveMinimum: 0or document that0is treated as omitted.src/tools/text.test.ts:350-361currently asserts the bug (change.obj.width === 0,height === 0). Update it to assert non-degenerate geometry.baseShapeunit test for the text zero-area refusal and the non-finite refusal.Acceptance criteria
penpot_add_textwith nowidth/heightstages a text shape whosewidthandheightare both strictly positive, withgrowTypestillauto-width, andselrect/pointsderived from that non-zero box.penpot_add_textwith an explicitwidth: 0behaves as "omitted" and produces a positive estimate, not a zero box (or is refused with a clear message — decide and document).baseShapethrows for atextshape withwidth <= 0orheight <= 0, and throws for any shape type with a non-finitex/y/width/height.add-obj/mod-objfor a text shape can reachpenpot_commitwith zero-area geometry; a pre-flight refusal names the offending shape and leaves the changeset staged.get-filewith non-zero:width/:heightand four distinct:points.penpot_add_text, README table) state that omitted dimensions are estimated, not left at zero.findings.md's "No auto-measurement is performed" gap is updated to reflect the guard (measured locally to a conservative estimate; Penpot still re-lays-out on open).Test plan
baseShaperefuses zero-area text; refusesNaN/Infinityon any type; accepts a positive text box.0.penpot_add_text(no dimensions, one dimension, both dimensions,width: 0) produces the expected positive geometry andgrowType.text.test.tscase that previously assertedwidth === 0now asserts a positive width/height.PENPOT_URL+PENPOT_TOKEN+ scratch file): stage one auto-width text with no dimensions,penpot_commit, re-read viaget-file, assert non-zero:width/:heightand four distinct:points; then open the file in the browser and confirm an edit saves without a 500.w0 h0auto-width text outside the guard), confirm the guard blocks it beforeupdate-fileis called.Out of scope / notes
nilgeometry with an actionable message instead of degrading toNaN, and/or clamp:auto-widthtext to a minimum box. That is an upstream suggestion, not this issue.gotcha #2is specific to the nREPL repair path (:ignore-geometry? trueemits:points/:selrectas operations, tripping the change validator). Our tools send complete shape objects and must keep lettingbaseShapederive geometry.penpot-bug-1e70e922-REPORT.mdand its bundle (penpot-bug-1e70e922-raw.bin,…-decoded.transit.json,…-zero-size-shapes.edn) live outside this repo in~/Projects/penpot-skill/; the pre-fix raw blob sha256 is4b23873c8947e4547268cd9a47d0b4feb75d07399c2e057af1053b2834ce983eand is the baseline to compare against.pi-loop opened and merged a pull request for this issue: #265