mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Copy: pare product claims to what we can certify, cut rule-of-three cadence, record arch 10 §10
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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."
|
||||||
|
|||||||
@@ -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’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’s import / export buttons to move your library between devices.
|
Use the header’s import / export buttons to move your library between devices.
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user