Copy: pare product claims to what we can certify, cut rule-of-three cadence, record arch 10 §10

This commit is contained in:
2026-06-26 23:09:47 +03:00
parent c19857b0a7
commit 43d3b480d9
7 changed files with 92 additions and 35 deletions
+5 -1
View File
@@ -155,7 +155,11 @@ role`) or the rule it demonstrates. - **Positional sub-section cross-refs.** Cit
14. **User-facing copy**: keep user-visible strings centralized and written for users (sentence 14. **User-facing copy**: keep user-visible strings centralized and written for users (sentence
case, active voice, no "please", no exclamation marks in errors). If/when an i18n layer case, active voice, no "please", no exclamation marks in errors). If/when an i18n layer
exists, route strings through it instead of hardcoding. exists, route strings through it instead of hardcoding. **Product claims** (landing,
About, onboarding, value props) follow `arch 10 §10`: claim only what we can certify, no
absolutes (never/always/fully/everything), no durability the platform doesn't back ("saved",
not "permanent"), and state a posture once per surface — reduce uncertain promises, keep the
real ones.
15. **Chart-builder guidance reasons over role, not raw type** (`src/core/chart-builder.ts`): 15. **Chart-builder guidance reasons over role, not raw type** (`src/core/chart-builder.ts`):
a `builderWarnings` rule (or any measure/dimension decision) must ask the post-transform a `builderWarnings` rule (or any measure/dimension decision) must ask the post-transform
+21 -3
View File
@@ -2,7 +2,7 @@
A browser-based **snippet manager for [Vega-Lite](https://vega.github.io/vega-lite/) A browser-based **snippet manager for [Vega-Lite](https://vega.github.io/vega-lite/)
visualizations**. Author chart specs as JSON, watch them render live, and keep a personal, visualizations**. Author chart specs as JSON, watch them render live, and keep a personal,
searchable library — fully local, offline-capable, no account. searchable library — local, offline-capable, no account.
> Astrolabe is a **spec-driven rebuild** on an architecture adapted from its sibling > Astrolabe is a **spec-driven rebuild** on an architecture adapted from its sibling
> project Syto. The authoritative behavioral contract lives in [`docs/spec/`](docs/spec/). > project Syto. The authoritative behavioral contract lives in [`docs/spec/`](docs/spec/).
@@ -23,10 +23,28 @@ npm run typecheck
npm test # Vitest npm test # Vitest
``` ```
## What it does
- **Editor + live preview** — write Vega-Lite specs as JSON in Monaco with schema-aware
validation and autocomplete; the chart re-renders as you type.
- **Snippet library** — save, search, tag, and organize specs. Each snippet carries a stable
published version plus a separate editable draft, so you can tinker without losing a
known-good copy.
- **Reusable datasets** — store data once (inline, or fetched once from a URL and snapshotted)
and reference it by name from many snippets.
- **Chart Builder** — a no-JSON on-ramp that generates a spec from field, mark, and encoding
choices. The JSON stays the source of truth and is always editable.
- **Theming & fonts** — custom chart themes with a visual Theme Builder, a curated font roster,
and your own uploaded font faces.
- **Local-first** — your library lives in the browser (IndexedDB); offline-capable and installable
(PWA), with import/export for backup and transfer. No account, no server.
## Status ## Status
**M0 — Skeleton.** Toolchain green (typecheck, tests, build, PWA). The three-pane shell Active development, **pre-1.0** and not yet publicly released — well past the initial milestones
renders; features land milestone by milestone per the implementation plan (MVP at end of M1). and usable day to day; the first public release will be `1.0.0`. Behavior is specified in
[`docs/spec/`](docs/spec/) and the architecture patterns in
[`docs/architecture/`](docs/architecture/00-overview.md).
## License ## License
@@ -683,6 +683,39 @@ choice isn't re-litigated per surface:
_(Consulted via /council → WAI-ARIA APG tabs/accordion/disclosure, IBM Carbon _(Consulted via /council → WAI-ARIA APG tabs/accordion/disclosure, IBM Carbon
accordion usage, NN/g #6/#8.)_ accordion usage, NN/g #6/#8.)_
## 10. Product claims & promise copy
Declarative copy — the landing, the About modal, onboarding, empty-state value props —
makes **claims** about the product, not just feedback about an action. The care the §3
triad gives error wording applies here too: **say only what we can certify, and say it
once.** An overstated claim reads as insecurity, and the first time a user catches one
being false it costs more trust than the claim ever bought (NN/g credibility; GOV.UK
"don't oversell"). The test is not modesty for its own sake — it is that every sentence
survives a skeptical reading.
- **Claim what we can certify — not the future, not what we don't control.** "No account,
no server" is structural and always true (there is no backend). "Never leave your machine"
is a vow over every future build and every edge case; state the posture instead — "stored
locally on your device; there's no server to send them to."
- **No absolutes.** _never · always · fully · entirely · everything._ One edge case or one
future feature falsifies them, and the reader feels the overreach even when it happens to
hold. Prefer the scoped form: "works offline" over "fully offline"; "your library lives in
the browser" over "everything lives in the browser."
- **Don't promise durability the platform doesn't back.** Browser storage (IndexedDB, no
`persist()`) is best-effort and the browser may evict it. A chart is **saved**, not kept
forever — route the permanence claim through **export**, which is the real backup.
- **State a posture once per surface.** Repeating "local / no account / no server / offline"
across the hero, the lede, and a feature grid is three chances to sound unsure of it. Give
the posture one home and let the other surfaces describe the product.
- **Match the register, and don't under-sell.** Astrolabe is a free, spare-time tool: the
voice is plain and matter-of-fact, not manifesto. But concrete, true capabilities —
portable Vega-Lite JSON, two authoring modes, custom themes — are claims worth making
plainly. Reducing promises means cutting the _uncertain_ ones, never the real ones.
SOUL.md §"Local-Only by Default" is the internal **intent** and may be absolute; this
section governs how that intent is **phrased to users**, where the promise should be only as
strong as we can keep.
--- ---
## Do / Don't ## Do / Don't
@@ -694,6 +727,8 @@ accordion usage, NN/g #6/#8.)_
- Adopt the APG keyboard pattern for new widgets; route all global keys through the one - Adopt the APG keyboard pattern for new widgets; route all global keys through the one
router. router.
- Mark **optional** fields, not required ones (GOV.UK) — e.g. "Comment (optional)". - Mark **optional** fields, not required ones (GOV.UK) — e.g. "Comment (optional)".
- In product claims, say only what we can certify, once per surface; route durability
through export.
- Consult `/council` when this contract is silent — then record the answer back here. - Consult `/council` when this contract is silent — then record the answer back here.
**Don't** **Don't**
@@ -709,3 +744,5 @@ accordion usage, NN/g #6/#8.)_
- Don't ship a **dead disabled control** as a placeholder for an unbuilt feature — a - Don't ship a **dead disabled control** as a placeholder for an unbuilt feature — a
disabled button explains nothing and is skipped by assistive tech (GOV.UK, NN/g). Omit the disabled button explains nothing and is skipped by assistive tech (GOV.UK, NN/g). Omit the
action until it works, then show it enabled (e.g. "Build Chart" appears with M4). action until it works, then show it enabled (e.g. "Build Chart" appears with M4).
- Don't use absolutes in product claims (never/always/fully/everything) or promise what the
platform can't keep — say "saved," not "permanent."
+10 -10
View File
@@ -47,9 +47,8 @@ export function AboutModal() {
v<span className={styles.version}>{__APP_VERSION__}</span> v<span className={styles.version}>{__APP_VERSION__}</span>
</p> </p>
<p className={styles.body}> <p className={styles.body}>
A local-first workspace for authoring, organizing, and previewing Vega-Lite charts. Edit A local-first workspace for authoring and organizing Vega-Lite charts. Edit the JSON,
JSON, see the chart update live, and keep a personal library of snippets with no watch it render live, and keep a personal library of snippets.
account, no server, and full offline support.
</p> </p>
</section> </section>
@@ -74,17 +73,18 @@ export function AboutModal() {
<section className={styles.section}> <section className={styles.section}>
<h3 className={styles.heading}>Privacy</h3> <h3 className={styles.heading}>Privacy</h3>
<p className={styles.body}> <p className={styles.body}>
Astrolabe runs entirely in your browser. Your snippets, datasets, and settings are stored Astrolabe runs in your browser. Your snippets, datasets, and settings are stored locally
locally and never leave your machine. on your device there&rsquo;s no server to send them to.
</p> </p>
<ul className={styles.list}> <ul className={styles.list}>
<li>No account, no sign-in, no server-side storage.</li> <li>No account or sign-in.</li>
<li>The app runs no analytics or tracking no cookies, no telemetry, no profiling.</li>
<li> <li>
The only outbound network requests are ones you create: URL-sourced datasets you add The app itself runs no analytics or tracking. Its host (Cloudflare) records standard,
yourself. aggregate traffic like any web server not your library or the charts you build, which
stay in your browser.
</li> </li>
<li>After the first load, the app works fully offline.</li> <li>The only outbound requests are ones you make: datasets you load from a URL.</li>
<li>After the first load, the app works offline.</li>
<li> <li>
Use the header&rsquo;s import / export buttons to move your library between devices. Use the header&rsquo;s import / export buttons to move your library between devices.
</li> </li>
+2 -2
View File
@@ -137,8 +137,8 @@ export function Onboarding() {
<div className={styles.inner}> <div className={styles.inner}>
<h2 className={styles.title}>Welcome to Astrolabe</h2> <h2 className={styles.title}>Welcome to Astrolabe</h2>
<p className={styles.tagline}> <p className={styles.tagline}>
A local library for your Vega-Lite charts authored as JSON, rendered live, and kept on A local library for your Vega-Lite charts write the spec as JSON, watch it render live,
your device. and keep it all on your device.
</p> </p>
<div className={styles.ctaRow}> <div className={styles.ctaRow}>
+4 -4
View File
@@ -1,10 +1,10 @@
/** /**
* Project feedback channel. * Project feedback channel.
* *
* There is no server and no tracker (see the About modal — no telemetry of any * The app sends no telemetry of its own (see the About modal); feedback is a
* kind); feedback is a plain email the user composes and sends from their own * plain email the user composes and sends from their own client. The address is
* client. The address is a Cloudflare Email Routing alias that forwards to the * a Cloudflare Email Routing alias that forwards to the author, so it can be
* author, so it can be retired without exposing or churning a personal inbox. * retired without exposing or churning a personal inbox.
* *
* Shared by the Support modal and the About modal — a contact address is worth * Shared by the Support modal and the About modal — a contact address is worth
* a single source of truth so the two surfaces can't drift. * a single source of truth so the two surfaces can't drift.
+13 -15
View File
@@ -369,8 +369,7 @@ export function Landing(): ReactNode {
</h1> </h1>
<p> <p>
Astrolabe is a local studio for Vega-Lite. Write a spec by hand or build one by Astrolabe is a local studio for Vega-Lite. Write a spec by hand or build one by
clicking, give it a theme, and keep all your charts in one library in the browser, clicking, give it a theme, and keep all your charts in one searchable library.
with no account and no server.
</p> </p>
<div className={styles.heroCta}> <div className={styles.heroCta}>
<a className={`${styles.btn} ${styles.btnPrimary}`} href="/app/"> <a className={`${styles.btn} ${styles.btnPrimary}`} href="/app/">
@@ -399,8 +398,8 @@ export function Landing(): ReactNode {
</h2> </h2>
<p className={styles.capP}> <p className={styles.capP}>
The editor is Monaco with the Vega-Lite schema loaded, so you get validation, The editor is Monaco with the Vega-Lite schema loaded, so you get validation,
autocompletion, and inline docs without going online. Each chart keeps a draft and a autocompletion, and inline docs without going online. Each chart keeps an editable
published version: edit the draft, publish when it's ready, revert when it isn't. draft alongside a published version you can revert to.
</p> </p>
<p className={styles.capP}> <p className={styles.capP}>
If you'd rather not start from JSON, the builder works from the kind of chart you If you'd rather not start from JSON, the builder works from the kind of chart you
@@ -426,7 +425,7 @@ export function Landing(): ReactNode {
<div className={styles.capText}> <div className={styles.capText}>
<div className={styles.capEyebrow}>One library</div> <div className={styles.capEyebrow}>One library</div>
<h2 className={styles.capH}> <h2 className={styles.capH}>
Every chart you make <b>stays</b>, with the data behind it. Every chart you make is <b>saved</b>, with the data behind it.
</h2> </h2>
<p className={styles.capP}> <p className={styles.capP}>
Store a dataset once and reference it by name from as many charts as you want. Rename Store a dataset once and reference it by name from as many charts as you want. Rename
@@ -434,8 +433,8 @@ export function Landing(): ReactNode {
</p> </p>
<p className={styles.capP}> <p className={styles.capP}>
Load data by pasting CSV or JSON, or by fetching a URL. You can also lift inline data Load data by pasting CSV or JSON, or by fetching a URL. You can also lift inline data
out of a spec into a shared dataset. Search, sort, and duplicate as the collection out of a spec into a shared dataset. Search and sort as the collection grows, and
grows. duplicate a snippet to start a variant.
</p> </p>
</div> </div>
<div className={styles.shot}> <div className={styles.shot}>
@@ -599,17 +598,16 @@ export function Landing(): ReactNode {
Your charts are <b>yours.</b> Your charts are <b>yours.</b>
</h2> </h2>
<p className={styles.creedLede}> <p className={styles.creedLede}>
Astrolabe runs in your browser and stores everything locally — no account, no server. Astrolabe runs in your browser, and what it makes is ordinary Vega-Lite JSON read it,
What it produces is ordinary Vega-Lite JSON, so you can read it, edit it in other tools, edit it in other tools, or take it elsewhere whenever you want.
or keep it long after you've stopped using Astrolabe.
</p> </p>
<div className={styles.creedGrid}> <div className={styles.creedGrid}>
<div> <div>
<span className={styles.creedKey}>private</span> <span className={styles.creedKey}>private</span>
<h3>Stays on your machine</h3> <h3>Local to your browser</h3>
<p> <p>
Charts, data, and themes are saved in your browser. Install it and it works with no Charts, data, and themes are saved in this browser, and it keeps working offline
connection. once installed.
</p> </p>
</div> </div>
<div> <div>
@@ -624,8 +622,8 @@ export function Landing(): ReactNode {
<span className={styles.creedKey}>yours</span> <span className={styles.creedKey}>yours</span>
<h3>Set up your way</h3> <h3>Set up your way</h3>
<p> <p>
Two ways to author, your own fonts and themes, your own library. Arrange it to match Author by hand or by clicking, with your own fonts and themes. Arrange the library
how you work. to match how you work.
</p> </p>
</div> </div>
</div> </div>