You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
What is the shape of messages.json, and how is rule 7 enforced? #11
Resolved by grilling. One English messages.json, nested by section to mirror src/sections/, ~200 strings, static import with compiler-checked access.
Shape
Each top-level key matches a section directory name, so sections/Faq.tsx reads m.faq and nothing else. List content lives in the same file as arrays of objects — splitting "UI labels" from "marketing copy" would require a per-string judgement that does not survive contact with copy edits.
Rendered by a RichText base component that maps tone to a token role and br to <br>. Rejected: splitting into five plain keys, which freezes the emphasis boundaries into JSX and leaves the client unable to read or reword the sentence as a sentence; and an inline [[…]] convention, which collides with the bracket placeholder convention below.
Excerpt
{
"site": { "name": "Execile", "phone": "(555) 000-0000", "phoneHref": "tel:+15550000000" },
"hero": {
"badge": "Medical billing & RCM for independent practices",
"title": [ "Collect Every Dollar You've ", { "text": "Earned", "tone": "brand" }, ".",
{ "br": true },
"Cut Denials ", { "text": "Before They Happen", "tone": "success" }, "." ],
"lead": "Specialty-assigned billing for independent practices with 1–20 providers. We work inside your existing EHR, report what we collect, and quote our price upfront.",
"ctaPrimary": "Get a Free Billing Assessment",
"ctaSecondary": "See Our Services",
"trustPoints": [
"HIPAA Compliant", "BAA on Day One", "No Setup Fees",
"No Long-Term Contract", "US-facing team · 24-hour processing"
]
},
"problem": {
"eyebrow": "The problem",
"title": "Revenue leaks quietly, one claim at a time.",
"cards": [
{ "stat": "[X%]", "title": "Rising denials",
"blurb": "Denial rates keep climbing while appeal windows quietly close." },
{ "stat": "[X days]", "title": "Aged AR nobody works",
"blurb": "Claims past 90 days sit untouched until they time out." },
{ "stat": "[X%]", "title": "No revenue visibility",
"blurb": "You learn about collection problems weeks after they happen." },
{ "stat": "[X wks]", "title": "Providers not enrolled",
"blurb": "New hires see patients before payers will pay for them." }
]
},
"faq": {
"eyebrow": "FAQ",
"title": "Common questions",
"items": [
{ "q": "Do you work inside our existing EHR?",
"a": "Yes. We work inside your existing EHR/PM — no software switch and no re-training." }
]
},
"assessment": {
"badge": "Free · No obligation",
"title": "Get a Free Billing Assessment",
"form": {
"label": "Billing assessment request",
"fields": {
"name": { "label": "Full name" },
"email": { "label": "Work email" },
"specialty": { "label": "Specialty", "placeholder": "Select…",
"options": ["[SPECIALTY 1]", "[SPECIALTY 2]", "[SPECIALTY 3]",
"Primary Care", "Behavioral Health", "Physical Therapy",
"Urgent Care", "Internal Medicine", "Pediatrics", "Other"] },
"message": { "label": "Message", "hint": "(optional)" }
},
"submit": "Get a Free Billing Assessment",
"disclaimer": "HIPAA-compliant · BAA before any data moves · No obligation.",
"noscript": "JavaScript is required to send this form. Please call (555) 000-0000 instead."
},
"success": {
"title": "Request received",
"body": "We'll reach out within one business day to schedule your assessment and send the BAA."
}
}
}
Consumers write import { m } from '~/messages' and read m.faq.items[0].q. resolveJsonModule infers the shape, so key paths are compiler-checked with no codegen step and no runtime lookup — it is a static import that prerenders and dead-code-eliminates. Generated types were rejected as a build step reproducing what the compiler already infers; the only loss is literal types (arrays widen to string[]), which costs nothing with no locale layer (rule 13).
Messages above is written as a hand-maintained type only where it constrains authoring — Segment[] and IconName — applied via satisfies in messages.ts.
Placeholders
The canvas's bracket convention is kept verbatim. Every remaining placeholder in the content:
[X%] · [X days] · [X wks] · [X] · [Y%] · [Y days] · [SPECIALTY 1..3] · [Specialty] · [STARTUP NAME] · [Street address] · [City, ST ZIP] · [domain] · [PLACEHOLDER — replace with real client results] · [One-line summary of what changed and why.]
A pnpm placeholders script walks the JSON and prints each one with its key path:
This turns "the client updates the copy at the end" into a checklist that can be verified empty. It exits non-zero while placeholders remain, so it can gate a future production deploy without gating v1 — the standing constraint keeps placeholders in the launch copy deliberately.
Enforcement
react/jsx-no-literals applies everywhere, including base/. Base components render {children} and carry no literal text; exempting the directory would open the hole the rule exists to close.
Its blind spot: the rule catches JSX text children only. aria-label, alt, title and placeholder are user-facing and pass through unflagged. Partial mitigation on the component side — IconButton requires a label prop, Field requires label, Icon is aria-hidden unless given a title — so the common cases are structurally forced through a prop. Attribute strings remain a review matter.
This is the fourth item in the rule-enforcement gap on #15, joining literal token values, ramp-key leakage, and the style escape hatch.
Notes for other tickets
What is the project scaffold, and what runs on commit? #15: confirm Oxlint ships react/jsx-no-literals; the Vite+ research asserted the mapping but it is unverified against the actual rule set. Add the placeholders script to package scripts.
Results mode (see CONTEXT.md) is a build-time flag, not a message. messages.json carries both results.items and results.comingSoon; the flag picks which renders.
Activity
Abuubkar commentedon Sep 5, 2026
Resolved by grilling. One English
messages.json, nested by section to mirrorsrc/sections/, ~200 strings, static import with compiler-checked access.Shape
Each top-level key matches a section directory name, so
sections/Faq.tsxreadsm.faqand nothing else. List content lives in the same file as arrays of objects — splitting "UI labels" from "marketing copy" would require a per-string judgement that does not survive contact with copy edits.Rich text — one string, one mechanism
The hero heading is the only string in the canvas needing emphasis, and the only one containing a
<br>. It is encoded as segments, not HTML:Rendered by a
RichTextbase component that mapstoneto a token role andbrto<br>. Rejected: splitting into five plain keys, which freezes the emphasis boundaries into JSX and leaves the client unable to read or reword the sentence as a sentence; and an inline[[…]]convention, which collides with the bracket placeholder convention below.Excerpt
{ "site": { "name": "Execile", "phone": "(555) 000-0000", "phoneHref": "tel:+15550000000" }, "hero": { "badge": "Medical billing & RCM for independent practices", "title": [ "Collect Every Dollar You've ", { "text": "Earned", "tone": "brand" }, ".", { "br": true }, "Cut Denials ", { "text": "Before They Happen", "tone": "success" }, "." ], "lead": "Specialty-assigned billing for independent practices with 1–20 providers. We work inside your existing EHR, report what we collect, and quote our price upfront.", "ctaPrimary": "Get a Free Billing Assessment", "ctaSecondary": "See Our Services", "trustPoints": [ "HIPAA Compliant", "BAA on Day One", "No Setup Fees", "No Long-Term Contract", "US-facing team · 24-hour processing" ] }, "problem": { "eyebrow": "The problem", "title": "Revenue leaks quietly, one claim at a time.", "cards": [ { "stat": "[X%]", "title": "Rising denials", "blurb": "Denial rates keep climbing while appeal windows quietly close." }, { "stat": "[X days]", "title": "Aged AR nobody works", "blurb": "Claims past 90 days sit untouched until they time out." }, { "stat": "[X%]", "title": "No revenue visibility", "blurb": "You learn about collection problems weeks after they happen." }, { "stat": "[X wks]", "title": "Providers not enrolled", "blurb": "New hires see patients before payers will pay for them." } ] }, "faq": { "eyebrow": "FAQ", "title": "Common questions", "items": [ { "q": "Do you work inside our existing EHR?", "a": "Yes. We work inside your existing EHR/PM — no software switch and no re-training." } ] }, "assessment": { "badge": "Free · No obligation", "title": "Get a Free Billing Assessment", "form": { "label": "Billing assessment request", "fields": { "name": { "label": "Full name" }, "email": { "label": "Work email" }, "specialty": { "label": "Specialty", "placeholder": "Select…", "options": ["[SPECIALTY 1]", "[SPECIALTY 2]", "[SPECIALTY 3]", "Primary Care", "Behavioral Health", "Physical Therapy", "Urgent Care", "Internal Medicine", "Pediatrics", "Other"] }, "message": { "label": "Message", "hint": "(optional)" } }, "submit": "Get a Free Billing Assessment", "disclaimer": "HIPAA-compliant · BAA before any data moves · No obligation.", "noscript": "JavaScript is required to send this form. Please call (555) 000-0000 instead." }, "success": { "title": "Request received", "body": "We'll reach out within one business day to schedule your assessment and send the BAA." } } }Typed access
Consumers write
import { m } from '~/messages'and readm.faq.items[0].q.resolveJsonModuleinfers the shape, so key paths are compiler-checked with no codegen step and no runtime lookup — it is a static import that prerenders and dead-code-eliminates. Generated types were rejected as a build step reproducing what the compiler already infers; the only loss is literal types (arrays widen tostring[]), which costs nothing with no locale layer (rule 13).Messagesabove is written as a hand-maintained type only where it constrains authoring —Segment[]andIconName— applied viasatisfiesinmessages.ts.Placeholders
The canvas's bracket convention is kept verbatim. Every remaining placeholder in the content:
[X%]·[X days]·[X wks]·[X]·[Y%]·[Y days]·[SPECIALTY 1..3]·[Specialty]·[STARTUP NAME]·[Street address]·[City, ST ZIP]·[domain]·[PLACEHOLDER — replace with real client results]·[One-line summary of what changed and why.]A
pnpm placeholdersscript walks the JSON and prints each one with its key path:This turns "the client updates the copy at the end" into a checklist that can be verified empty. It exits non-zero while placeholders remain, so it can gate a future production deploy without gating v1 — the standing constraint keeps placeholders in the launch copy deliberately.
Enforcement
react/jsx-no-literalsapplies everywhere, includingbase/. Base components render{children}and carry no literal text; exempting the directory would open the hole the rule exists to close.Its blind spot: the rule catches JSX text children only.
aria-label,alt,titleandplaceholderare user-facing and pass through unflagged. Partial mitigation on the component side —IconButtonrequires alabelprop,Fieldrequireslabel,Iconisaria-hiddenunless given atitle— so the common cases are structurally forced through a prop. Attribute strings remain a review matter.This is the fourth item in the rule-enforcement gap on #15, joining literal token values, ramp-key leakage, and the
styleescape hatch.Notes for other tickets
react/jsx-no-literals; the Vite+ research asserted the mapping but it is unverified against the actual rule set. Add theplaceholdersscript to package scripts.noscriptand success copy all come fromm.assessment.form/m.assessment.success.CONTEXT.md) is a build-time flag, not a message.messages.jsoncarries bothresults.itemsandresults.comingSoon; the flag picks which renders.Abuubkar commentedon Sep 5, 2026
Resolved. Schema, excerpt, RichText segment encoding, typed access, placeholder script and enforcement in the resolution comment.
Scaffold: TanStack Start + StyleX + Cloudflare, running with no accounts