Repository navigation
docs: add "Form Library Agnostic" post and cover rasterize script - #471
Conversation
Formalize the blog cover pipeline: rasterize-cover.mjs screenshots a 1200x630 cover SVG in headless Chromium and writes the sibling PNG, replacing the ad-hoc Playwright recipe prose in blog/CLAUDE.md (which also referenced a cloud-only /opt/pw-browsers path). playwright joins the docs devDependencies on the same range bulma-ui already pins. blog/CLAUDE.md now requires a hero/cover image for any post syndicated to dev.to or Medium, and documents the manual Medium import flow.
Post 4 of the blog revival series (#378): the design story of the form suite. Field/Control composition, context-based auto-wrap and skip-if-wrapped, the six *Base escape hatches, the validate-it-yourself pattern, and the full input set, framed around what the reader keeps (plain React state, portable validation, no lock-in) rather than what the library omits. Ships a pixel-art RPG character-creation cover (hand-built SVG plus a 1200x630 PNG raster via the new rasterize:cover script) and opts into dev.to syndication.
|
Warning Review limit reached
Next review available in: 18 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughThe pull request adds a blog post about building forms with plain React and bestax-bulma. It also adds a Playwright-based SVG-to-PNG cover-image command and updates publishing guidance. ChangesForms Documentation
Cover Rasterization
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 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 |
Preview DeploymentPreview URL: https://b2694c5f.bestax.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/blog/2026-08-04-forms-without-a-form-library.md`:
- Around line 98-100: Update the Email field example so the rendered label is
associated with InputBase: either wrap the control in a label without
labelProps/id, or provide a matching htmlFor through labelProps and id on
InputBase. Apply the change to the Field example while preserving its existing
email input behavior.
- Line 162: Update the controlled-component guidance in the surrounding
paragraph to avoid claiming every input uses value, onChange, and message.
Describe those props as a common pattern for native-like inputs, and instruct
readers to adapt library field state and validation output to each component’s
documented value and callbacks, including Autocomplete’s onInput and onSelect.
In `@docs/scripts/rasterize-cover.mjs`:
- Around line 35-44: Update the rasterization flow in rasterize-cover so that,
after confirming svgPath exists and before page.screenshot(), it parses the SVG
and validates width="1200", height="630", and a full-bleed background rect
covering the canvas. Reject invalid or missing requirements with actionable
console errors and a nonzero exit, and synchronize the relevant cover
requirements documentation with the enforced behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 26afc7a5-cfe0-4dfa-ac0d-0e29b7cb4a1a
⛔ Files ignored due to path filters (3)
docs/static/img/forms-without-a-form-library.pngis excluded by!**/*.pngdocs/static/img/forms-without-a-form-library.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (4)
docs/blog/2026-08-04-forms-without-a-form-library.mddocs/blog/CLAUDE.mddocs/package.jsondocs/scripts/rasterize-cover.mjs
The post now says outright that you can use the validation library of your choice: named in the hook and the description, and the Validate It Yourself section shows a zod schema feeding the same color, message, and messageColor props (plus the react-hook-form wiring in a sentence) instead of leaving the idea as a closing aside.
Preview DeploymentPreview URL: https://a13339dd.bestax.pages.dev |
Building Forms Without a Form Library read to some as bestax having no form components. Form Library Agnostic states the actual stance: full form suite, no prescribed state or validation layer. The cover art carries the title in pixel lettering, so the SVG and PNG are regenerated with the new title (and the banner alt text follows). The slug and filename keep the descriptive forms-without-a-form-library phrase for the URL.
Preview DeploymentPreview URL: https://5c3df522.bestax.pages.dev |
There was a problem hiding this comment.
Deep review — 0 blocking · 1 advisory
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🔵 Advisory | Robustness | rasterize:cover depends on Playwright Chromium, which pnpm's script-blocking hardening won't auto-download; the script degrades gracefully with install instructions and isn't run in CI, so this is on the record, not a blocker. |
docs/scripts/rasterize-cover.mjs:46 |
Overall: This is a clean, accurate docs PR. I chased every load-bearing technical claim to source and each one checks out: the FormContext snippet is verbatim, the six Base exports are exactly six, the wrap-behavior table matches the actual useInsideField/useInsideControl logic in each component (full-wrap for Input/Select/TextArea/Slider/Rate, Field-only for File/Numberinput/Taginput/Autocomplete, no-wrap for Checkbox/Radio/Switch/*Base), and the "Field accepts color but renders nothing" caveat is real (color: _fieldColor is destructured and dropped). The riskiest part for a docs post is broken internal links under onBrokenLinks: throw; I couldn't run the build here, so the human's fastest confidence check is that the CI docs build stays green.
Residual risk:
- Broken internal links — refuted by hand: all 20 link targets (
api/form/*,datetime/{dateinput,timeinput,datetimeinput},guides/library/form,skills/form,blog/v3-forms-release) exist as files, and the PR reports theonBrokenLinks: throwbuild green. - Silently-wrong live-demo props (react-live isn't type-checked at build) — refuted:
iconLeftName,messageColor,color,labelProps, andNotificationcolor/isLightall exist in source, and the author exercised the demos in the dev server. - Prose over-generalization — line 63 ("an input checks both hooks before wrapping") technically holds only for the full-wrap inputs, but the table at line 212 explicitly refines it for the Field-only set, so no reader is misled.
🏄 Total mellow set, dude. Every claim in this post actually catches the wave it says it does, links all land clean, and the no-form-library stance is stated once, factually, no hype foam. Ship it.
Per review: the composed Field/InputBase snippet now models the same labelProps htmlFor plus id association the post teaches, and the bring-your-own-library paragraph scopes the value/onChange claim to the text-style inputs, noting checked on the boolean controls and Autocomplete's value/onInput/onSelect surface.
Per review: refuse an SVG whose root lacks width=1200 height=630 or a full-bleed background rect instead of silently writing a wrong-size or letterboxed PNG. Verified against all three existing covers (forms, floor, state-of-react) and two synthetic bad fixtures. blog/CLAUDE.md notes the script enforces the contract.
Preview DeploymentPreview URL: https://6d47d4e0.bestax.pages.dev |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Pull request overview
Adds post 4 of the blog-revival series from #378 and formalizes cover-image rasterization.
Changes:
- Adds the form-library-agnostic article with live demos.
- Adds SVG/PNG cover artwork and syndication guidance.
- Adds a Playwright-based SVG-to-PNG script.
Reviewed changes
Copilot reviewed 4 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
docs/blog/2026-08-04-forms-without-a-form-library.md |
Adds the article and demos. |
docs/static/img/forms-without-a-form-library.svg |
Adds source cover artwork. |
docs/static/img/forms-without-a-form-library.png |
Adds rasterized cover artwork. |
docs/scripts/rasterize-cover.mjs |
Adds cover rasterization and validation. |
docs/package.json |
Registers the command and Playwright. |
pnpm-lock.yaml |
Locks the Playwright dependency. |
docs/blog/CLAUDE.md |
Documents cover and syndication workflows. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Number(attr('x') ?? 0) === 0 && | ||
| Number(attr('y') ?? 0) === 0 && | ||
| Number(attr('width')) === WIDTH && | ||
| Number(attr('height')) === HEIGHT && | ||
| (attr('fill') ?? 'none') !== 'none' |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/blog/2026-08-04-forms-without-a-form-library.md`:
- Around line 166-192: Add the missing zod import before the schema declaration
in the ZodSignup example so the existing z.object and z.string calls resolve
correctly.
In `@docs/scripts/rasterize-cover.mjs`:
- Around line 50-52: Replace the regex-based rootTag/rootAttr parsing in the
rasterization script with an XML parser that reads the actual SVG root element
and its rect attributes, ensuring namespaced or prefixed attributes such as
data-width are not treated as width and supporting single quotes and whitespace
around equals. Update the related attribute consumers to use parsed values, and
add regression cases covering these invalid and valid attribute forms.
- Around line 65-73: Strengthen the full-bleed validation around the fullBleed
predicate in rasterize-cover.mjs: reject transparent fills and zero effective
opacity, including fill-opacity and opacity attributes, and ensure the SVG
viewBox/aspect-ratio contract cannot introduce rendered transparent margins.
Validate rendered coverage before writing the PNG, or explicitly enforce a
compatible viewBox contract while preserving the existing rectangle dimension
checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: db95ea5b-9140-4bae-9bf6-5448f12a5001
⛔ Files ignored due to path filters (2)
docs/static/img/forms-without-a-form-library.pngis excluded by!**/*.pngdocs/static/img/forms-without-a-form-library.svgis excluded by!**/*.svg
📒 Files selected for processing (3)
docs/blog/2026-08-04-forms-without-a-form-library.mddocs/blog/CLAUDE.mddocs/scripts/rasterize-cover.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/blog/CLAUDE.md
Per review: the ZodSignup fence used z without defining it; readers copying the snippet now get the import line.
Per review: the regex checks could be fooled by single-quoted or whitespace-padded attributes and data-width decoys, and never proved the background actually paints (fill=transparent, opacity=0, and fill-opacity=0 all passed, and a mismatched viewBox could letterbox despite correct rect geometry). The page is already open in Chromium, so validation now reads the parsed DOM: root width/height attributes, an exact 0 0 1200 630 viewBox when one is present, and a full-bleed rect proved opaque via getBBox and computed fill, opacity, and fill-opacity. No new dependency. Verified against the three existing covers plus six adversarial fixtures (decoy attributes, transparent and zero-opacity fills, wrong viewBox, wrong dims, no background) and a single-quoted-attribute SVG that the regex version wrongly rejected.
Preview DeploymentPreview URL: https://9900f9c1.bestax.pages.dev |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 7 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (4)
docs/blog/2026-08-04-forms-without-a-form-library.md:142
- The validation state is currently visual only:
Inputdoes not derivearia-invalidor an accessible description fromcolor/message. Since this is the pattern readers are meant to copy, expose the invalid state and associate/announce the generated error text.
color={error ? 'danger' : undefined}
message={error}
messageColor={error ? 'danger' : undefined}
docs/blog/2026-08-04-forms-without-a-form-library.md:190
- This alternate validation example repeats the accessibility gap and also omits the
id/htmlForpairing required by the article's own guidance. Associate the label and error with the input and expose the invalid state so this copyable example remains accessible.
color={error ? 'danger' : undefined}
message={error}
messageColor={error ? 'danger' : undefined}
docs/scripts/rasterize-cover.mjs:89
getBBox()reports the rectangle's untransformed local geometry. A cover containing a translated or scaled 1200×630 rect therefore passes this full-bleed check even when the rendered rect leaves part of the viewport uncovered, defeating the script's letterbox guard. Check viewport-space bounds instead.
const box = rect.getBBox();
docs/blog/2026-08-04-forms-without-a-form-library.md:41
- This generated help text is not automatically associated with the input, so assistive technology will not receive the description. Give the message content an ID and reference it with
aria-describedby.
This issue also appears in the following locations of the same file:
- line 140
- line 188
message="One tag, and you get the label, the icon, and this help text."
The page.evaluate callback runs in Chromium, not Node, so document and getComputedStyle tripped no-undef in CI lint. Same global comment precedent as capture-stories.mjs.
Preview DeploymentPreview URL: https://9e315fcd.bestax.pages.dev |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 7 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
docs/scripts/rasterize-cover.mjs:46
- The optional output path is not constrained to PNG, while
page.screenshot()infers its format from the extension. Passingout.jpgorout.webptherefore succeeds but produces a non-PNG asset even though this command and its success message promise a PNG. Reject non-.pngoutput paths (or forcetype: 'png'and require a matching extension).
const pngPath = pngArg ? resolve(pngArg) : svgPath.replace(/\.svg$/, '.png');
docs/scripts/rasterize-cover.mjs:101
- This check only reads the rectangle's own
opacity. SVG group opacity is not inherited, so a full-size rect inside<g opacity="0">still reportsopacity: 1, passes validation, and allows a cover with no painted background to be rasterized. Include ancestor visibility and opacity when determining whether the rect is actually visible.
style.fill !== 'none' &&
style.fill !== 'rgba(0, 0, 0, 0)' &&
Number(style.opacity) > 0 &&
Number(style.fillOpacity) > 0
docs/blog/2026-08-04-forms-without-a-form-library.md:191
- This example renders a visible
labelbut omits theid/htmlForassociation that the post identifies as required above. As written, clicking “Email” will not focus this input and assistive technology cannot reliably associate the label with it. Add the same explicit association used by the live demos.
label="Email"
docs: codify the framing, lint, and reviewer lessons from #471
|
🎉 This PR is included in version 4.0.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 5.8.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 2.0.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Closes #378
What
docs/blog/2026-08-04-forms-without-a-form-library.md, post 4 of the revival series. Structure per the issue: hook, Field/Control auto-wrap, skip-if-wrapped, the Base escape hatch, validate-it-yourself, the full input set, documentation. Threetsx livedemos: the auto-wrappingInput, an addons layout proving skip-if-wrapped, and the userland validation pattern from the bestax-form skill. Framing sells what the reader keeps (plain React state, portable validation, bring-your-own library welcome) and states the no-form-library stance once, factually.publish_to_devto: true,canonical_url,image/cover_image).docs/scripts/rasterize-cover.mjsplus arasterize:coveralias and aplaywrightdevDependency indocs/package.json(same range bulma-ui pins). Formalizes the SVG-to-PNG cover step that previously lived as prose.docs/blog/CLAUDE.md: syndicated posts (dev.to or Medium) must now ship a hero/cover image; the cover sections point at the script instead of the ad-hoc recipe; adds the manual Medium import flow.Verification
pnpm exec turbo run build --filter=@allxsmith/bestax-docsgreen on the final content (onBrokenLinks: throwvalidates every internal link); no missing-truncate warningpnpm format:checkclean; em-dash sweep on the new post and SVG: zero hitsbuild/.devto-publish/copy has fully absolutized links and cover; built pageog:imagepoints at the absolute PNG URLrasterize:coversmoke-tested against the existing floor-is-react-18 cover (byte-identical dimensions) and used to produce this posts PNGNote: the filename date is 2026-08-04; per
blog/CLAUDE.md, rename before merging if publishing slips past that date.Summary by CodeRabbit
Documentation
New Features