Files
improvise/docs/index.html
T
Edward Langley f1011d7f6f feat(docs): publish the context/ prose as a site folded into Pages
The landing page is the site root and the app is /app/; the prose now sits
at /docs/, assembled by the same web/assemble.sh the Pages job and the
browser harness both run, so what ships and what is tested are one tree.

Published: design-notes, design-principles, repo-map, the three ADRs, the
seven context/code/ arguments, and the 501 test-evidence entries as their
own clearly-labelled section. Not published: context/plan.md, which is an
agent prompt about a launch rather than a description of the program, and
context/cloud-agents.md, which is addressed to nobody who visits a website.
Neither is referenced by a published document outside a code fence, so
leaving them out dangles nothing. The reasoning is context/code/doc-site.md,
which is itself published.

Two things the corpus needed that a generator would not have given it. The
~190 improvise-xxxx citations resolve to a generated ticket index built from
the already-public .beads/issues.jsonl rather than to an invented URL that
would 404; an id the export does not yet carry stays plain text. And a code
span that is wholly a repo path links to GitHub at the assembled ref, but
only when the file exists on disk, so a stale citation degrades to text.

The generator link-checks its own output and exits non-zero on a break, and
assemble.sh runs under set -eu, so a dead cross-reference fails the assembly
rather than shipping. It earned that on its first two runs: it caught a
fence-close regex comparing a three-character marker to a one-character
string, which swallowed every document from its first code block onward, and
a list parser that ended the gotcha list at the first blank line, emitting 1
of the 48 anchors the jump strip links to. Proved by injection afterwards --
renaming the anchors reports 48 missing anchors, pointing the ticket links at
an ungenerated page reports 763 missing pages.

No framework, no node_modules for the site, no build step beyond one
zero-dependency node script, matching the landing page's posture.

improvise-djoo

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 13:37:20 -07:00

216 lines
6.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>improvise — terminal pivot-table modeling</title>
<meta
name="description"
content="Terminal pivot-table modeling in the spirit of Lotus Improv: multidimensional cells, formulas over dimensions instead of cell addresses, vim-style keybindings."
/>
<!-- improvise-e61: the site root, linking to the app under ./app/.
Why cols/rows are 120x37 and not the 100x30 the tickets name: see the
close reasons on improvise-e61 and improvise-kh8. -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.css"
/>
<style>
:root {
--bg: #14161a;
--panel: #1b1e24;
--fg: #d7dae0;
--dim: #8b93a1;
--accent: #8ec07c;
--rule: #2b2f37;
--mono: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
}
main {
max-width: 1000px;
margin: 0 auto;
padding: 3rem 1rem 5rem;
}
h1,
h2 {
font-family: var(--mono);
font-weight: 600;
letter-spacing: -0.01em;
}
h1 {
font-size: 2.4rem;
margin: 0 0 0.4rem;
color: var(--accent);
}
h2 {
font-size: 1.15rem;
margin: 3rem 0 0.3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--rule);
}
.tagline {
color: var(--dim);
font-size: 1.1rem;
max-width: 62ch;
margin: 0 0 1.5rem;
}
p.note {
color: var(--dim);
margin: 0 0 1rem;
max-width: 72ch;
}
/* Same size as the body text and set off, not a grey footnote: a
visitor who reads the section reads this (improvise-2oxt). */
p.dated {
margin: 0 0 1rem;
padding: 0.6rem 0.9rem;
max-width: 72ch;
border-left: 3px solid var(--accent);
background: var(--panel);
}
a {
color: var(--accent);
}
.cta {
display: inline-block;
margin: 0 0.6rem 0.6rem 0;
padding: 0.55rem 1.1rem;
border: 1px solid var(--rule);
border-radius: 6px;
background: var(--panel);
font-family: var(--mono);
text-decoration: none;
}
.cta:hover {
border-color: var(--accent);
}
.cta.primary {
border-color: var(--accent);
color: var(--bg);
background: var(--accent);
font-weight: 600;
}
.player {
background: var(--panel);
border: 1px solid var(--rule);
border-radius: 6px;
overflow: hidden;
}
pre {
background: var(--panel);
border: 1px solid var(--rule);
border-radius: 6px;
padding: 1rem;
overflow-x: auto;
font-family: var(--mono);
font-size: 0.9rem;
}
footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--rule);
color: var(--dim);
font-size: 0.9rem;
}
</style>
</head>
<body>
<main>
<h1>improvise</h1>
<p class="tagline">
Terminal pivot-table modeling in the spirit of Lotus Improv —
multidimensional cells, formulas over dimensions instead of cell
addresses, and vim-style keybindings for reassigning axes on the fly.
</p>
<p>
<a class="cta primary" href="./app/"
>▶ Run improvise in your browser</a
>
<a class="cta" href="./docs/">Documentation</a>
<a class="cta" href="https://github.com/fiddlerwoaroof/improvise"
>Source on GitHub</a
>
</p>
<p class="note">
No install, no server, no account: the whole application runs in a Web
Worker on this machine and the document lives in your browser's private
storage.
</p>
<h2>Reassign an axis</h2>
<p class="note">
A category lives on exactly one axis. Moving it rewrites the grid;
nothing in the data or the formulas changes.
</p>
<div class="player" id="cast-pivot"></div>
<h2>Drill into an aggregate</h2>
<p class="note">
An aggregated cell opens onto the records behind it, editable in place.
</p>
<p class="dated">
Recorded 2026-04-09. The records grid has changed since: it now has a
column per measure plus columns for adding a category and a measure,
where this recording shows a single <code>Value</code> column.
</p>
<div class="player" id="cast-drill"></div>
<h2>Formulas over dimensions</h2>
<p class="note">
<code>Profit = Revenue - Cost</code> names categories and items, not
<code>B3</code>, so it survives every rearrangement of the view.
</p>
<div class="player" id="cast-formulas"></div>
<h2>Import a CSV</h2>
<p class="note">
The wizard classifies each column as a category, a measure or a time
field, and builds the model from what you confirm.
</p>
<div class="player" id="cast-import"></div>
<h2>Install</h2>
<pre>
# Nix
nix profile install github:fiddlerwoaroof/improvise
# Prebuilt binaries (Linux x86_64/aarch64, macOS Intel and Apple Silicon)
# https://github.com/fiddlerwoaroof/improvise/releases</pre
>
<p class="note">
Not yet on crates.io; see the releases page for the current prerelease.
</p>
<footer>
Apache-2.0 ·
<a href="https://github.com/fiddlerwoaroof/improvise">GitHub</a> ·
<a href="./app/">Live browser demo</a> ·
<a href="./docs/">Documentation</a>
</footer>
</main>
<script src="https://cdn.jsdelivr.net/npm/asciinema-player@3.10.0/dist/bundle/asciinema-player.min.js"></script>
<script>
// One loop rather than four copies of the same call: the config is the
// same for every cast and a second spelling of it is a second answer.
for (const name of ["pivot", "drill", "formulas", "import"]) {
AsciinemaPlayer.create(
`./casts/${name}.cast`,
document.getElementById(`cast-${name}`),
// No idleTimeLimit: every cast header already carries 2.0, and a
// second spelling of it here is a second answer to one question.
{ cols: 120, rows: 37, theme: "monokai", autoPlay: false, loop: true },
);
}
</script>
</body>
</html>