docs(adr): fold the user's answers on ASCII ids, virtual categories and scripts into ADR 007 (improvise-cq1q.1)

- Id alphanumerics are ASCII and include -: slugify maps everything
  outside a-z, 0-9 and - to _, collapses _, trims _ and - at both ends,
  prefixes n before a digit; the id syntax, examples and the suffix /
  hyphenated-slug collision rule follow.
- A file never declares a virtual category and refers to one by its id.
- Only CSV import and the .improv loaders read display names in input
  text; command-language text, from any source, names things by id.
- The formula line is shown before and after; the dimension spelling and
  the [_measure] tag follow from the rules, so that question is deleted.
- The Opus review of 2dfe0886: each measure is keyed by one handle, and
  the shared-display case moves to an open question; views join the
  things a formula may name.

Open questions are now three: a formula and a stored measure with the
same display, where stored measures are declared, and whether interactive
typing keeps a display stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (adr007-steward)
2026-10-09 09:18:17 -07:00
committed by Edward Langley
parent 2dfe088668
commit 8405f5c18b
2 changed files with 188 additions and 145 deletions
+181 -140
View File
@@ -19,15 +19,16 @@ amendment is approved:
| clause | accepted rule | the amendment proposes |
|---|---|---|
| 1 | groups have no id | a group has an id and a display, and its id behaves like every other id |
| 1 | `_index`, `_dim`, `_measure` are the only ids beginning with `_` | a leading `_` is reserved for virtual ids in general, including the virtual views `_records` and `_drill`; virtual ids are never written to `.improv` |
| 1 | `_index`, `_dim`, `_measure` are the only ids beginning with `_` | a leading `_` is reserved for virtual ids in general, including the virtual views `_records` and `_drill`; a file never declares a virtual category and refers to one by its id (`_measure`) |
| 2 | `CellKey` pairs are `(CategoryId, ItemId)` | `(CategoryId, ItemHandle)` |
| 2, 5 | a collapsed entry and a group reference name a group by name | by its id |
| 4 | casefold; a run of U+0020 spaces becomes one `_`; strip characters outside `a`–`z`, `0`–`9`, `-`, `_`; remove a leading `_` | casefold; every non-alphanumeric character becomes `_`; runs of `_` collapse; leading and trailing `_` are removed; `n` is prepended to a slug that starts with a digit |
| 4 | casefold; a run of U+0020 spaces becomes one `_`; strip characters outside `a`–`z`, `0`–`9`, `-`, `_`; remove a leading `_` | casefold; every character outside `a`–`z`, `0`–`9` and `-` becomes `_`; runs of `_` collapse; leading and trailing `_` and `-` are removed; `n` is prepended to a slug that starts with a digit |
| 5 | a hand-written id uses the alphabet `a`–`z`, `0`–`9`, `-`, `_` and may begin with `_` only if reserved | a hand-written id obeys every syntax rule of a generated id |
| 6 | display names typed into minibuffers are turned into ids at the input edge | only CSV import and the `.improv` loaders turn display names in input text into ids; command-language text, from any source, names things by id |
| 12 | a typed name new to a formula edit is resolved against the document at commit | the client resolves it, ids first, and the wire carries only id-form text |
| 12 | the regression test commits the **unedited** text after the concurrent renames | the test edits a non-name token and commits, because a client may pass unedited text as a no-op without resolving any name |
Four questions are open (last section).
Three questions are open (last section).
## Context
@@ -64,7 +65,8 @@ human name moves to a new `display` field.
existing thing. A reference to the stable identity cannot glitch when something is
renamed or replaced.
- **Display names are for people.** They appear on screen, are typed into
minibuffers, and are turned into ids at the input edge.
minibuffers, and are turned into ids at the input edge. **[Amendment]** Clause 6 narrows
which readers turn display text into ids.
### 1. What carries an identity
@@ -96,15 +98,21 @@ have ids `_records` and `_drill`, displayed `_Records` and `_Drill`; they are ad
on the wire (`SwitchView`), and a slugified `_Drill` would otherwise be `drill` and
could collide with a user's view. Slugify never produces a leading `_` (clause 4), so
the reservation needs no check in the grammar.
- **[Amendment] Virtual ids are lower case and are never written to `.improv`.** The
code, the wire, `ViewState` and the browser client spell the virtual categories
`_Index`, `_Dim` and `_Measure` today, and move to the lower-case ids in the re-key
step (clause 11). How the file records what it records today under `_Measure`,
`_Index` and `_Dim` is open question 2.
- **[Amendment] Virtual ids are lower case.** The code, the wire, `ViewState` and the
browser client spell the virtual categories `_Index`, `_Dim` and `_Measure` today,
and move to the lower-case ids in the re-key step (clause 11).
- **[Amendment] A file never declares a virtual category; it refers to one by its
id.** The virtual categories exist in every document, so they are not required in
the file. Where a line refers to one, it spells the virtual id:
`_measure: column`, `_index: none`, `region=east, _measure=revenue = 1200`. An
old-version file's `_Measure` resolves through its display at load, like every name
in an old file (clause 5), and the first save writes `_measure`. Where the file
declares the stored measures themselves (`Revenue`, `Cost`) is open question 2.
- **[Amendment] Records mode's synthetic positions are values, not ids.** The row
coordinate under `_index` and the column coordinate under `_dim` (a column index,
gotcha 1) are values of those virtual categories. They are never persisted or typed,
take no part in id uniqueness, and cannot collide with a user's item.
coordinate under `_index` is a value of that virtual category. So, by the same
reasoning, is the column coordinate under `_dim` (a column index, gotcha 1). They are
never persisted or typed, take no part in id uniqueness, and cannot collide with a
user's item.
**Formulas and measures.** **[Amendment]** A measure is the normal formula target
(`profit = revenue - cost`). A dimension (a category other than `_Measure`) is a rare
@@ -112,21 +120,23 @@ one, for a derived dimension.
- **A `_Measure` target has no item of its own**, and its handle is its formula's
`FormulaId`. So the category panel's `_Measure` rows use
`ItemHandle::{Item(ItemId), FormulaTarget(FormulaId)}` (clause 6).
- **[Amendment] A formula is a kind of measure, so a `_Measure` formula's id is the
measure's id.** A measure has an id and a display, as everything else does.
`Profit = Revenue - Cost` is one thing, `profit`: another formula refers to it as
`profit`, each Profit cell's `CellKey` holds `profit` under `_measure`, and
`ReplaceFormula` and `RemoveFormula` address `profit`. A formula that derives a
measure already stored under that display takes the stored measure's id, so the
column has one id, not two. Removing the formula removes a measure that exists only
through it; a measure that also holds stored data stays, as a stored measure.
- **[Amendment] A formula is a kind of measure, so there is no distinction between a
`_Measure` formula's id and its measure's id.** A measure has an id and a display,
as everything else does. `Profit = Revenue - Cost` is one thing, `profit`: another
formula refers to it as `profit`, each Profit cell's `CellKey` holds `profit` under
`_measure`, and `ReplaceFormula` and `RemoveFormula` address `profit`.
- **[Amendment] Each measure's cells are keyed by one handle.** A stored measure is
keyed `ItemHandle::Item(id)`. A measure that exists only through its formula is
keyed `ItemHandle::FormulaTarget(id)`. The id is the measure's either way, and no
column is keyed by both. What happens when a formula's measure and a stored measure
share a display, and what removing that formula does, is open question 1.
- **[Amendment] A target outside `_Measure` is a real item**, with its own `ItemId`,
and its formula has a separate `FormulaId`. A formula whose generated id would
collide takes a suffix like any other generated id (clause 4): a formula `Tax` in
`CustomCat` gets item id `tax`, then formula id `tax-<n>`.
- **[Amendment] Formulas may refer to anything by id.** A function in a formula may
accept any id-bearing thing (a category, an item, a measure, a formula, a group),
referred to by its id. Whether a given function takes the thing itself or its value
accept any id-bearing thing (a category, an item, a measure, a formula, a view, a
group), referred to by its id. Whether a given function takes the thing itself or its value
(an item's display, say) is part of that function's signature. Global uniqueness
makes every such reference unambiguous, and a function may read any part of a record,
so nothing further is needed here.
@@ -196,27 +206,27 @@ thing's id never depends on how it was made. An id is lower case.
1. **Casefold, with Unicode full case folding**, so `Straße` → `strasse`. Rust's
`str::to_lowercase` is not enough (`ß` stays `ß`). The folding table or crate must
pass the `wasm` gate, because `improvise-core` is in the browser bundle.
2. **Replace every character that is not alphanumeric with `_`.** That includes
spaces, tabs and every other whitespace, punctuation and `-`. Whether
"alphanumeric" means ASCII only is open question 1. Whichever it decides, the slug
ends up in `a`–`z`, `0`–`9` and `_`: a Unicode reading needs a further step that
drops or transliterates what is outside that alphabet, and question 1 picks it.
2. **Replace every character outside `a`–`z`, `0`–`9` and `-` with `_`.** The id's
alphanumerics are ASCII and include `-`. Every other character, including spaces,
tabs and other whitespace, punctuation and every non-ASCII letter, becomes `_`.
3. **Collapse each run of `_` to one `_`**, whether the underscores came from step 2
or were in the display.
4. **Remove every leading and trailing `_`.** A leading `_` is reserved for virtual
ids (clause 1), and a trailing one is dropped.
4. **Remove every leading and trailing `_` and `-`**, repeating until neither end
holds either. A leading `_` is reserved for virtual ids (clause 1), a trailing `_`
is dropped, and no id begins or ends with `-`.
5. **If the slug starts with a digit, prepend `n`.** An id is never purely numeric
and never starts with a digit, so `2024` becomes `n2024`.
6. **If nothing is left, use `<kind>-<n>`**, such as `category-1` or `item-4`. `n`
comes from the shared counter below.
comes from the shared counter below. A display of only `-`, only `_` or only
non-ASCII letters ends here, because `-` alone is not an id.
So a generated id is a lower-case letter followed by letters, digits and single
underscores, never ending in `_`, plus, only when it collided, a `-<n>` suffix; or
`<kind>-<n>`. The syntax of an id is therefore a slug,
`[a-z][a-z0-9]*(_[a-z0-9]+)*`, optionally followed by `-` and a number; a `-` anywhere
else never occurs.
**The syntax of an id** is therefore: a lower-case ASCII letter, then any of `a`–`z`,
`0`–`9`, `-` and single underscores, never ending in `_` or `-`:
`[a-z]([a-z0-9-]|_(?=[a-z0-9-]))*`, not ending in `-`. Slugify's output already has
that shape, so slugifying an id gives the same id back. The collision suffix `-<n>`
(below) keeps the shape too, because it ends in a digit.
Examples under the amendment, with "alphanumeric" read as ASCII:
Examples under the amendment:
| display | id |
|---|---|
@@ -224,24 +234,40 @@ Examples under the amendment, with "alphanumeric" read as ASCII:
| `Time Period`, `Time Period`, `Time\tPeriod`, `Time \u{200B} Period` | `time_period` |
| `Coastal_East` | `coastal_east` |
| `a_ b`, `a__b` | `a_b` |
| `Q1-2024`, `Q1 - 2024` | `q1_2024` |
| `Q1-2024` | `q1-2024` |
| `Q1 - 2024` | `q1_-_2024` |
| `East-1` | `east-1` |
| `East `, `East_`, `-East-` | `east` |
| `_Draft` | `draft` |
| `Region(code)` | `region_code` |
| `Café` | `caf` |
| `Crème Brûlée` | `cr_me_br_l_e` |
| `Straße` | `strasse` |
| `2024`, `_2024` | `n2024` |
| `2024 Budget` | `n2024_budget` |
| `2024-Q1` | `n2024-q1` |
| `Ü`, `-`, `%` | `<kind>-<n>` |
Under the accepted steps, the ones that differ are `Q1-2024` → `q1-2024`,
`Q1 - 2024` → `q1_-_2024`, `East ` → `east_`, `a_ b` → `a__b`, `Region(code)` →
`regioncode`, `2024` → `2024` and `-` → `-`.
Under the accepted steps, the ones that differ are `East ` → `east_`, `a_ b` →
`a__b`, `Time\tPeriod` → `timeperiod`, `Time \u{200B} Period` → `time__period`,
`-East-` → `-east-`,
`Region(code)` → `regioncode`, `Crème Brûlée` → `crme_brle`, `2024` → `2024`,
`2024-Q1` → `2024-q1` and `-` → `-`.
**Collisions.** `Time Period` and `Time_Period` slug alike, and whichever comes later
takes a suffix. **[Amendment]** So do `East`, `East ` and `East_`, and `Q1-2024` and
`Q1 2024`, which slug alike only under the amendment's steps. `East` in `Region` and
`East` in `Direction` also slug alike, and ids are global, so the second is `east-<n>`.
takes a suffix. **[Amendment]** So do `East`, `East ` and `East_`, which slug alike
only under the amendment's steps. `East` in `Region` and `East` in `Direction` also
slug alike, and ids are global, so the second is `east-<n>`.
**[Amendment] A suffix and a hyphenated slug can look alike.** The display `East-1`
slugs to `east-1`, which is also what the counter's suffix may make of `east`. They
never collide, because every id a document holds is taken, whoever made it:
- if `east-1` is already held (as a slug or as a suffix), a display `East-1` that slugs
to it is a collision, and becomes `east-1-<n>`;
- if the counter's candidate `east-<n>` is already held, the counter advances until a
candidate is free.
So an id says nothing about how it was made: `east-1` may be a suffixed `east` or the
slug of `East-1`.
- **Any colliding generated id gets a suffix**, whatever kind of thing holds the
slug: on import, when an id-less file loads, and when the host creates something in
a session. An id that does not collide stays bare.
@@ -267,11 +293,9 @@ every data key and reference in that file spells it. So:
- **two identical hand-written ids in one file are refused at load**, even when they
declare different kinds of thing. Renaming one would corrupt the data section, and a
file with a duplicate cannot be resaved safely. The error names both lines.
- **[Amendment] A hand-written id obeys every syntax rule of a generated id**: a slug
(lower case, starting with a letter, single underscores, not ending in `_`),
optionally followed by the `-<n>` suffix. So `east-1` and `item-4`, which every save
writes, load back; `-east`, `east-`, `2024` and `_draft` do not. One that breaks the
syntax is refused at load (clause 5).
- **[Amendment] A hand-written id obeys every syntax rule of a generated id** (the
syntax above). So `east-1`, `item-4` and `q1_-_2024` load; `-east`, `east-`, `2024`,
`a__b` and `_draft` are refused at load (clause 5).
**Reuse across a save and reload is accepted.** Retired ids are not persisted. So
remove `East`, save, reload and add `East`, and the new item gets `east` again. An
@@ -286,9 +310,9 @@ promised (improvise-8tk5).
**Built code.** `crates/improvise-core/src/model/id.rs` (`slugify`, `IdAllocator`)
implements the accepted steps, and `context/code/core.md` §20 describes them. Once
the amendment is approved, a follow-up must:
- replace every non-alphanumeric character, not only spaces, with `_`, and collapse
`_` runs;
- trim a trailing `_`, and prepend `n` before a leading digit;
- replace every character outside `a`–`z`, `0`–`9` and `-`, not only spaces, with
`_` instead of stripping it, and collapse `_` runs;
- trim a trailing `_` and both ends' `-`, and prepend `n` before a leading digit;
- make `claim` refuse every hand-written id a generated one could not be;
- add the virtual views to `VIRTUAL_IDS`, and `IdKind::Group`.
@@ -312,16 +336,17 @@ grammar spells it **with ranges**, because the generator drops `ASCII_ALPHANUMER
`id_tag`, so it has no id. The error names the line;
- a hand-written id beginning with `_`, other than a reserved one;
- **[Amendment]** a hand-written id that breaks any syntax rule of a generated id
(clause 4). Virtual ids are never written to a file, so no hand-written id begins
with `_`. The grammar's `id_token` stays permissive, and the loader refuses, so the
error can name the line.
(clause 4). A file never declares a virtual category, so no declared id begins with
`_`; a reference may still spell a virtual id such as `_measure`. The grammar's
`id_token` stays permissive, and the loader refuses, so the error can name the line.
**Inside formula text an id may still need quoting.** The formula grammar's
`bare_ident` must begin with a letter or `_`, and keywords are guarded (gotcha 34). So
the writer spells an id in formula text through `quote::quote_name`. An id equal to a
keyword (`sum`, `if`, `where`, `is`) is written `|sum|`, and under the accepted rules
an id beginning with a digit (`2024`) is written `|2024|`. **[Amendment]** No id begins
with a digit, so only keyword ids need quoting.
with a digit, so only keyword ids need quoting. A `-` inside an id needs none, because
the formula grammar's bare identifier already admits `-` after its first character.
```pest
id_token = @{ ('a'..'z' | '0'..'9' | "_" | "-")+ }
@@ -366,16 +391,39 @@ lose its axis line to the view's number format, and the new-version arm puts
- a view whose number format is each axis keyword round-trips in a document where
that view also holds a category `format` on an axis.
**[Amendment] A formula line** is
`- <target>(<formula id>) = <id-form expression> [<category id>]`:
- **The parenthesised id is the formula's `FormulaId`**, between the target and the
`=`. For a measure formula it is also the measure's id.
- **For a measure formula, `<target>` is the measure's display**, held on the
`Formula`: `- Profit(profit) = revenue - cost`. How a dimension formula's line spells
its target, and whether the `[_measure]` tag is written, is open question 3.
**[Amendment] A formula line** declares the formula: its target, its id, and its
expression over ids. Today's `web/demo.improv` and the format's own dimension example
are written:
```
## Formulas
- Profit = Revenue - Cost
- Tax = Revenue * 0.1 [CustomCat]
```
Under the amendment the same two formulas are written:
```
## Formulas
- Profit(profit) = revenue - cost
- tax(tax-1) = revenue * 0.1 [customcat]
```
- **A measure formula** (no category tag) is the normal case. The formula is the
measure (clause 1), so the line is the measure's only declaration: `Profit` is its
display, and `(profit)` is the id it shares with its formula. No `[_measure]` tag is
written, because a file does not spell a virtual category where it is not needed
(clause 1); a line without a tag targets `_measure`.
- **A dimension formula** (a `[<category id>]` tag) targets a real item, already
declared with its display in its category's section (`## Category: CustomCat(customcat)`
lists `- Tax(tax)`). So the line names the target by its item id, `tax`, and
`(tax-1)` is the formula's own id. The display is not repeated, because a display
lives in one place.
- **So the loader can always tell the two apart**: with a tag the target is an item
id; without one it is a measure's display.
- **The expression is over ids**, spelled through `quote::quote_name`. A reference to
a formula target is the id text of its `ItemHandle`: the item id outside `_Measure`,
the formula's (and measure's) id for a `_Measure` target.
the measure's (and its formula's) id for a `_Measure` target.
- **A `WHERE`/`IS` value follows the same rule**. It is the id text of an
`ItemHandle` in the filtered category, and the formula panel shows it as a display.
- **A display in the target position that looks like `Name(x)` is pipe-quoted**, as in
@@ -434,13 +482,17 @@ display that would otherwise read as display plus id:
is the group's id, and a new group is created by its display under the same
create-by-display rule;
- `AddFormula { target_category: CategoryId, raw }`.
- **[Amendment] Only a user interface accepts a display name as a reference**, and it
converts the name to an id as early as it can. The TUI and browser clients are user
interfaces, and so is the headless command line. The wire never carries a display as
a reference.
- **[Amendment] Scripts refer to things by id.** A script that names something by its
display fails. A script gets a display only through a dedicated function such as
`display_for(id)`. Which headless entry points count as scripts is open question 4.
- **[Amendment] Only two readers turn display names in input text into ids: CSV
import and the `.improv` loaders.** They read text people wrote, where names are
all there is. The wire never carries a display as a reference.
- **[Amendment] Command-language text names things by id.** Anything that uses the
command language as a scripting language is a script, wherever the text comes from
(a script file, `improvise cmd`, another program). A script that names something by
its display fails. A script gets a display only through a dedicated function such as
`display_for(id)`.
- **[Amendment] Interactive typing prefers ids and previews displays** (clause 12).
Whether an interactive input still resolves typed text that matches no id by
display is open question 3.
- **A frame cannot address a thing it creates**, because it does not know the id the
host will assign. A create-then-act compound becomes **one command**:
`create-and-switch-view` sends `[CreateView, SwitchView]` today and becomes
@@ -610,8 +662,9 @@ answers for those.
preferred wherever there is ambiguity, now and once an autocomplete exists.
- A typed name equal to an id names that id's thing, even where it is also another
thing's display.
- Otherwise a display that exactly one thing in scope has names that thing.
- A display matching several things is refused, and the refusal lists their ids.
- Otherwise a display that exactly one thing in scope has names that thing, and a
display matching several things is refused with a list of their ids. Whether this
display stage stays is open question 3.
- A name that resolves to nothing is refused, and the refusal names it.
- Ids are matched exactly, with no case folding.
- **Typing prefers ids and previews displays.** A view-layer autocomplete turns typed
@@ -647,7 +700,8 @@ without resolving any name (improvise-hexb).
(`east`, `east-1`), type `east` into an editor, in text not captured at open:
- it must bind A, and the test must fail under displays-first;
- `east-1` must bind B;
- a display that is nobody's id must still resolve by display.
- a display that is nobody's id must still resolve by display (pending open
question 3).
A fixture where no display equals an id proves nothing.
@@ -715,86 +769,73 @@ three things:
These are open, and nothing above depends on an answer except where it says so.
### 1. Does "alphanumeric" mean ASCII?
### 1. A formula and a stored measure with the same display: one measure, and what does removal do?
**The question.** Slugify replaces every non-alphanumeric character with `_`. Unicode
counts `é`, `ü` and `日` as alphanumeric, but an id's alphabet is `a`–`z`, `0`–`9`, `_`
(and `-`).
**The question.** A document stores a `Profit` measure: an item of `_measure` with id
`profit`, display `Profit`, holding data. The user then adds the formula
`Profit = Revenue - Cost`. A formula is a kind of measure with one id (clause 1), and
displays are unique within their scope (clause 2), so this cannot simply make a second
`Profit`. Today `measure_item_names` merges the two into one column.
**Why it matters.** It decides which handle keys the column (`Item(profit)` or
`FormulaTarget(profit)`, clause 1), what `RemoveFormula` leaves behind, and whether
documents that rely on today's merge keep loading.
**Options.**
- **(a) ASCII.** A non-ASCII letter becomes `_`: `Café` → `caf`, `Ü` → `<kind>-<n>`,
`Crème Brûlée` → `cr_me_br_l_e`.
- **(b) Unicode, then drop what is outside the alphabet.** A non-ASCII letter
disappears without splitting the word: `Café` → `caf`, `Crème Brûlée` →
`crme_brle`.
- **(c) Unicode, then transliterate** (`é` → `e`). `Café` → `cafe`, `Crème Brûlée` →
`creme_brulee`. This needs a transliteration table that passes the `wasm` gate.
- **(a) The formula attaches to the stored measure.** One measure, id `profit`, keyed
`Item(profit)`; the formula computes it. Removing the formula leaves the stored
measure and its data.
- **(b) Refuse it.** Adding a formula whose display is a stored measure's is refused,
as any duplicate display is. Existing files that hold both must be repaired at load.
- **(c) The formula replaces the stored measure.** Its stored data is dropped or kept
hidden.
**Why it matters.** It decides every id generated from a non-English display.
**Author's recommendation:** (a). It keeps today's behaviour, so existing documents
load unchanged, and a measure stays one thing with one id.
**Author's recommendation:** (a): it is the simplest reading of "non-alphanumeric
characters are replaced", and it needs no table.
### 2. Where does a file declare the stored measures?
### 2. How does a file record the virtual categories, if virtual ids are never written?
**The question.** A file never declares a virtual category (clause 1). But the stored
measures, such as `Revenue` and `Cost`, are items of `_measure`, and each needs a
declaration that carries its display and id. Today they are listed under
`## Category: _Measure`:
**The question.** Virtual ids are never written to `.improv` (clause 1). But today's
files record three things under the virtual categories (`web/demo.improv` has all of
them):
- the virtual categories' axis placements, such as `_Measure: column` and
`_Index: none`;
- the stored measures, under `## Category: _Measure`;
- each data key's measure, as `_Measure=Revenue`.
```
## Category: _Measure
- Revenue, Cost
```
**Why it matters.** Every document with data has measures, so every file needs some
way to say which measure a cell holds and where `_Measure` sits.
**Why it matters.** Without a declaration a measure has no display or id in the file,
and a new-version file refuses any undeclared id (clause 5).
**Options.**
- **(a) Reserved keywords.** The file keeps fixed spellings for the virtual categories
(`_Measure`, `_Index`, `_Dim`, or new lower-case keywords). They are part of the
format, not ids, so no id is written.
- **(b) Write the virtual ids** (`_measure: column`, `_measure=revenue`), an exception
to "never written".
- **(c) Leave the virtual categories implicit.** Their axis placements take defaults,
the measures section has no category header, and a data key's measure is written
without its category.
- **(a) Keep a section for the virtual category's items only**, spelled by its id:
`## Category: _measure` with `- Revenue(revenue), Cost(cost)`. The section declares
items, not the category.
- **(b) A dedicated section**, such as `## Measures`, with the same item lines.
- **(c) Declare a measure where it is first used**, in a data key. A data key holds
only an id, so the display would be lost.
**Author's recommendation:** (a). The file needs these facts, and a keyword says so
without turning a virtual id into file content.
**Author's recommendation:** (b). It names what the section holds, and keeps the
virtual category out of the file entirely.
### 3. How does a dimension formula's line spell its target, and is the `[_measure]` tag written?
### 3. Does interactive typing still resolve a display name that matches no id?
**The question.** A measure formula's line is `- Profit(profit) = revenue - cost`: the
measure's display, then its id (clause 5). A dimension formula (the rare case) has an
item id and a separate formula id. Its line may be:
- **(a)** `- tax(tax-1) = … [customcat]`: the item id in the target position. The
target position then holds a display for a measure and an id for a dimension.
- **(b)** `- Tax(tax-1)(tax) = … [customcat]`: display, formula id, item id. The target
position always holds a display.
**The question.** Only CSV import and the `.improv` loaders turn display names in input
text into ids (clause 6). Interactive typing (the TUI's minibuffers and `:` line, the
formula editor, the browser) prefers ids and shows the display as a preview
(clause 12). Clause 12's resolver also has a display stage: typed text that matches no
id but is exactly one thing's display names that thing. Does that stage stay?
Today's writer omits the category tag for a `_Measure` formula. Should the new writer
keep omitting `[_measure]`?
**Why it matters.** It fixes the formula-line grammar and its generator strategy.
**Author's recommendation:** (b), so one position never holds two kinds of thing;
keep omitting `[_measure]`, which also keeps the virtual id out of the file
(question 2).
### 4. Which headless entry points are scripts?
**The question.** User interfaces accept display names, and scripts refer to things by
id only (clause 6). The headless front ends are `improvise cmd '<line>'`, which runs
one or more command lines given on the command line, and `improvise script <file>`,
which runs a file of them.
**Why it matters.** It decides whether `improvise cmd 'add-item Region East'`-style
invocations that name existing things by display keep working.
**Why it matters.** Without it, a person typing `Revenue` in the formula editor must
type or pick the id `revenue`. With it, a typed display still works, and interactive
input is a third place that reads display names.
**Options.**
- **(a)** `improvise cmd` is a user interface and resolves displays; a script file is
a script.
- **(b)** Both are scripts: only the TUI and browser clients resolve displays.
- **(a) Keep the display stage** for interactive typing only. Command-language text
from scripts never gets it.
- **(b) Ids only.** Interactive inputs match ids; displays appear only as previews and
autocomplete suggestions, which insert the id.
**Author's recommendation:** (a). A person types `improvise cmd` lines as they type
`:` lines in the TUI; a script file is a program that should not break when something
is renamed.
**Recommendation:** none. "Prefer ids" leaves room for (a); "only import and the
loaders read display names" points to (b).
+7 -5
View File
@@ -1715,10 +1715,12 @@ to `east_`. Only *leading* underscores are removed (step 4).
**ADR 007's proposed amendment changes these rules, and this code predates it.**
The amendment (PR #326) is not yet approved. Its clause 4:
- every non-alphanumeric character becomes `_` (not only U+0020, and `-` too),
so `Q1-2024` slugs to `q1_2024` and `Region(code)` to `region_code`;
- every character outside `a`–`z`, `0`–`9` and `-` becomes `_` instead of being
stripped (not only U+0020), so `Region(code)` slugs to `region_code`, while
`Q1-2024` keeps its `-`;
- runs of `_` collapse, so `a_ b` and `a__b` both slug to `a_b`;
- leading and trailing `_` are removed, so `East ` → `east` and `-East-` → `east`;
- leading and trailing `_` and `-` are removed, so `East ` → `east`, `-East-` →
`east`, and `-` alone takes `<kind>-<n>`;
- `n` is prepended to a slug that starts with a digit, so `2024` → `n2024`;
- a hand-written id must obey every syntax rule of a generated one.
@@ -1751,8 +1753,8 @@ the alphabet (non-empty, `a`–`z`, `0`–`9`, `-`, `_`), the reserved leading
`_` (only `_index`, `_dim` and `_measure` may carry one, per clause 1, and
the match is exact: `_indexer` and `_measure-1` are Reserved, since "no other
id may start with `_`"), and
duplicates. It accepts `-`, `-x`, `x-` and `2024`, all of which the amendment
refuses (a hand-written id obeys the syntax of a generated one). It never renames.
duplicates. It accepts `-`, `-x`, `x-`, `a__b` and `2024`, all of which the
amendment refuses (a hand-written id obeys the syntax of a generated one). It never renames.
Refusing is the loader's job, and the loader
must name both lines of a duplicate; `ClaimError` carries only the id, so the
loader keeps the line numbers. The virtual views `_records` and `_drill` are