docs(adr): settle ADR 007's eleven open questions and state the decisions (improvise-cq1q.1)
The decisions are written into the clauses as plain design, tagged [Amendment]: a measure formula's id is the measure's id; formulas may refer to any id-bearing thing; ids always win, with the display as a preview; user interfaces accept display names and scripts use ids and display_for; slugify replaces every non-alphanumeric character with _, collapses _, trims _ at both ends and prefixes n before a digit; a hand-written id obeys the generated syntax; a leading _ is reserved for virtual ids, which are lower case and never written to .improv; records-mode positions are values; groups have a display and ordinary ids. All eleven questions are deleted. Five new ones are open: the -<n> suffix in hand-written ids, ASCII "alphanumeric", how a file records the virtual categories, the dimension formula line and [_measure] tag, and which headless entry points are scripts. core.md section 20's note follows the new slug rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
1190c80efe
commit
6e46787887
@@ -18,15 +18,16 @@ amendment is approved:
|
||||
|
||||
| clause | accepted rule | the amendment proposes |
|
||||
|---|---|---|
|
||||
| 1 | groups have no id | a group's identifier is an id |
|
||||
| 1 | `_index`, `_dim`, `_measure` are the only ids beginning with `_` | the virtual views also have reserved `_` ids: `_records`, `_drill` |
|
||||
| 2 | `CellKey` pairs are `(CategoryId, ItemId)` | `(CategoryId, ItemHandle)` (pending open question 1) |
|
||||
| 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` |
|
||||
| 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 | a run of U+0020 spaces becomes one `_`; only a leading `_` is removed | each whitespace character becomes `_`, runs of `_` collapse after the strip, and leading and trailing `_` and `-` are removed |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
Eleven questions are open (last section).
|
||||
Five questions are open (last section).
|
||||
|
||||
## Context
|
||||
|
||||
@@ -73,55 +74,79 @@ human name moves to a new `display` field.
|
||||
| item | yes | `ItemId` |
|
||||
| formula | yes | `FormulaId` |
|
||||
| view | yes | `ViewId` |
|
||||
| group | **[Amendment]** yes (open questions 9 and 10) | open question 10 |
|
||||
| group | **[Amendment]** yes, with a display | `GroupId` |
|
||||
| record | `RecordHash`, over ids (clause 8) | unchanged shape |
|
||||
|
||||
**An id always names the same thing, so ids are unique in the whole document**, across
|
||||
every kind. An item id names one item without its category. A `CellKey` still pairs
|
||||
each item with its category, and an operation that creates an item still names the
|
||||
parent category. **[Amendment]** 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 in a typed context. What exactly that allows is open question 2.
|
||||
parent category.
|
||||
|
||||
**[Amendment] Groups.** A group has an id and a display, like a category or an item,
|
||||
and its id behaves like every other id: it is unique in the document, generated by
|
||||
slugify (`group` is an empty slug's `<kind>`), declared beside the display in the file,
|
||||
referred to by id, created by display, and renamed by a display rename. In code,
|
||||
PR #336 (improvise-cq1q.12, open) already adds a private display to `Group` beside its
|
||||
key; under this amendment that key becomes the group's id.
|
||||
|
||||
**Reserved ids.** The virtual categories have ids `_index`, `_dim` and `_measure`,
|
||||
displayed `_Index`, `_Dim` and `_Measure`. **No other id may begin with `_`.**
|
||||
**[Amendment]** A leading `_` is reserved for virtual categories, measures and other
|
||||
entities. The virtual views have ids `_records` and `_drill`, displayed
|
||||
`_Records` and `_Drill`. They are never persisted, but they are addressed on the wire
|
||||
(`SwitchView`), and a slugified `_Drill` would otherwise be `drill` and could collide
|
||||
with a user's view. Which entities the reservation covers is open question 6. Whether
|
||||
records mode's synthetic positions under `_Index` and `_Dim` are ids is open question 7.
|
||||
Moving today's capitalised keys to these ids is open question 8.
|
||||
**[Amendment]** A leading `_` is reserved for virtual ids in general. The virtual views
|
||||
have ids `_records` and `_drill`, displayed `_Records` and `_Drill`; they are addressed
|
||||
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 3.
|
||||
- **[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.
|
||||
|
||||
**Formula targets.** **[Amendment]** A measure is the normal formula target
|
||||
**Formulas and measures.** **[Amendment]** A measure is the normal formula target
|
||||
(`profit = revenue - cost`). A dimension (a category other than `_Measure`) is a rare
|
||||
one, for a derived dimension.
|
||||
- **[Amendment] A target outside `_Measure` is a real item**, with its own `ItemId`,
|
||||
and its formula has a separate `FormulaId`.
|
||||
- **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 measure has an id and a display, as everything else does.
|
||||
Whether a formula-target measure keeps the formula's id or becomes an item with
|
||||
its own is open question 1. The `ItemHandle` text in clauses 2, 5, 6, 8 and 11
|
||||
depends on its answer.
|
||||
- **[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 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
|
||||
(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.
|
||||
|
||||
### 2. Storage keys by id; `display` is the only place a human name lives
|
||||
|
||||
- **Categories and views:** `Model::categories` and `Workbook::views` are keyed by id,
|
||||
and each `Category`, `Item` and `View` gains a `display: String`.
|
||||
and each `Category`, `Item` and `View` gains a `display: String`. **[Amendment]** So
|
||||
does `Group`.
|
||||
- **Cells:** a `CellKey` is a sorted list of `(CategoryId, ItemId)` pairs.
|
||||
**[Amendment]** The pairs are `(CategoryId, ItemHandle)`, so that a `_Measure`
|
||||
formula cell's coordinate is `FormulaTarget` without claiming an `ItemId` the
|
||||
document does not hold (pending open question 1).
|
||||
document does not hold.
|
||||
- The key stays sorted, now **by category id**, so the canonical form (gotcha 5)
|
||||
is kept with a different sort key.
|
||||
- `DataStore` interns ids exactly as it interns names today.
|
||||
- An id's text names one thing, because ids are unique across kinds.
|
||||
- **View configuration:** the axis maps, page selections, and hidden and collapsed sets
|
||||
are keyed by ids. **[Amendment]** A collapsed entry names its group by the group's
|
||||
id. Whether the category id stays beside it is part of open question 10.
|
||||
id.
|
||||
- **Formulas:** a formula's references are ids (clause 5), and the formula panel
|
||||
renders them as display names (clause 6).
|
||||
|
||||
@@ -158,68 +183,68 @@ copyable, compared as an integer.
|
||||
|
||||
**Every** id the app generates comes from the display name by **slugify**: on
|
||||
import, on interactive creation, and when an id-less `.improv` file is loaded. So a
|
||||
thing's id never depends on how it was made. An id is lower case, in the alphabet
|
||||
`a`–`z`, `0`–`9`, `-` and `_`.
|
||||
thing's id never depends on how it was made. An id is lower case.
|
||||
|
||||
The steps, in order, as the amendment states them **[Amendment]**:
|
||||
**The accepted steps:**
|
||||
1. casefold, with Unicode full case folding;
|
||||
2. replace each run of spaces with one `_`;
|
||||
3. strip every character that is not `a`–`z`, `0`–`9`, `-` or `_`;
|
||||
4. remove any leading `_`;
|
||||
5. if nothing is left, use `<kind>-<n>`.
|
||||
|
||||
**[Amendment] The steps become:**
|
||||
1. **Casefold, with Unicode full case folding**, so `Straße` → `strasse`. Rust's
|
||||
`str::to_lowercase` is not enough (`ß` stays `ß` and is then stripped). The folding
|
||||
table or crate must pass the `wasm` gate, because `improvise-core` is in the browser
|
||||
bundle.
|
||||
2. **Replace each whitespace character with `_`.** "Whitespace" is the Unicode
|
||||
`White_Space` property (`char::is_whitespace`): U+0009–U+000D, U+0020, U+0085,
|
||||
U+00A0, U+1680, U+2000–U+200A, U+2028, U+2029, U+202F, U+205F and U+3000.
|
||||
- A tab, a no-break space and an ideographic space each become `_`.
|
||||
- A zero-width space (U+200B) and a byte-order mark (U+FEFF) are not whitespace,
|
||||
and step 3 strips them.
|
||||
- The realistic case is a header pasted from a web page, where a no-break space
|
||||
stands for a space.
|
||||
3. **Strip every character outside the id alphabet.** This strips non-ASCII letters,
|
||||
so an id never needs quoting in an `.improv` reference.
|
||||
4. **Collapse each run of `_` to one `_`**, whether the underscores came from
|
||||
whitespace or were in the display. The step comes after the strip (open
|
||||
question 5).
|
||||
5. **Remove every leading and trailing `_` and `-`**, repeating until neither end
|
||||
holds either (open question 11).
|
||||
- A leading `_` is reserved (clause 1).
|
||||
- A trailing `_` is dropped.
|
||||
- An id never begins or ends with `-`.
|
||||
`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 2.
|
||||
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.
|
||||
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. A display of only `-`, `_` or non-ASCII letters
|
||||
ends here, because `-` alone is not an id.
|
||||
comes from the shared counter below.
|
||||
|
||||
The accepted text has five steps instead: casefold; replace each **run of spaces**
|
||||
with one `_`; strip; remove any **leading** `_`; `<kind>-<n>` if empty.
|
||||
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>`. Whether a hand-written id may carry that suffix is open question 1.
|
||||
|
||||
Examples under the amendment:
|
||||
Examples under the amendment, with "alphanumeric" read as ASCII:
|
||||
|
||||
| display | id |
|
||||
|---|---|
|
||||
| `East` | `east` |
|
||||
| `Time Period`, `Time Period`, `Time\tPeriod`, `Time\u{A0}Period`, `Time \u{200B} Period` | `time_period` |
|
||||
| `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` (only `_` runs collapse) |
|
||||
| `Q1-2024`, `Q1 - 2024` | `q1_2024` |
|
||||
| `East `, `East_`, `-East-` | `east` |
|
||||
| `_Draft` | `draft` |
|
||||
| `Region(code)` | `regioncode` |
|
||||
| `Region(code)` | `region_code` |
|
||||
| `Café` | `caf` |
|
||||
| `Straße` | `strasse` |
|
||||
| `Ü`, `-`, `---` | `<kind>-<n>` |
|
||||
| `2024`, `_2024` | `n2024` |
|
||||
| `2024 Budget` | `n2024_budget` |
|
||||
| `Ü`, `-`, `%` | `<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 `-` → `-`.
|
||||
|
||||
**Collisions.** `Time Period` and `Time_Period` slug alike, and whichever comes later
|
||||
takes a suffix. **[Amendment]** So do `East`, `East ` and `East_`, which slug alike
|
||||
only under the amendment's steps (under the accepted steps `East ` slugs to `east_`). `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_`, 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>`.
|
||||
- **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.
|
||||
- **The suffix is `-` plus the next value of one increment-only counter per
|
||||
document**, as a plain number (`east-1`, not `east-001`). The counter works like
|
||||
Common Lisp's `gensym`: it never goes down and is never reused, so no order holds
|
||||
among suffixes (`east-7` may follow `east` directly). **[Amendment]** A suffix ends
|
||||
in a digit, so a suffixed id still meets the amendment's step 5.
|
||||
among suffixes (`east-7` may follow `east` directly).
|
||||
- **Retirement.** Within a session a removed id is never handed out again. The
|
||||
counter is past any suffix it issued, and a removed bare slug stays reserved in
|
||||
memory.
|
||||
@@ -238,6 +263,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**: lower
|
||||
case, never starting with `_` or a digit, never ending in `_`, no runs of `_`, and no
|
||||
character slugify would replace. One that does not is 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
|
||||
@@ -252,19 +280,18 @@ 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:
|
||||
- test whitespace, not only U+0020;
|
||||
- collapse `_` after the strip;
|
||||
- trim a trailing `_` and both ends' `-`;
|
||||
- settle what `claim` refuses (open question 11);
|
||||
- add the virtual views to `VIRTUAL_IDS`;
|
||||
- add `IdKind::Group` if groups are generated (open question 10).
|
||||
- replace every non-alphanumeric character, not only spaces, with `_`, and collapse
|
||||
`_` runs;
|
||||
- trim a trailing `_`, 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`.
|
||||
|
||||
Nothing calls `slugify` yet.
|
||||
|
||||
### 5. Persistence: ids on declarations *and* references
|
||||
|
||||
- **Declarations** (section headers, item lines, formula lines) carry the display and
|
||||
the id.
|
||||
- **Declarations** (section headers, item lines, formula lines, and **[Amendment]**
|
||||
group definitions) carry the display and the id.
|
||||
- **Every reference** is the id: axis lines, `hidden:` and `collapsed:`, page
|
||||
selections, `## Aggregation`, `Initial View:`, `## Data` keys (ids are guaranteed
|
||||
unique, names might not be), and names inside formula text.
|
||||
@@ -278,14 +305,17 @@ grammar spells it **with ranges**, because the generator drops `ASCII_ALPHANUMER
|
||||
(`Sales(Copy)`), an empty tag or a tag with a trailing space does not match
|
||||
`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 is `-` (open question 11 asks whether a
|
||||
leading or trailing `-`, or a trailing `_`, is refused too).
|
||||
- **[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.
|
||||
|
||||
**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 beginning
|
||||
with a digit (`2024`) or equal to a keyword (`sum`, `if`, `where`, `is`) is written
|
||||
`|2024|` or `|sum|`.
|
||||
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.
|
||||
|
||||
```pest
|
||||
id_token = @{ ('a'..'z' | '0'..'9' | "_" | "-")+ }
|
||||
@@ -306,9 +336,10 @@ id_ref = { id_token }
|
||||
`model_name` has no id and keeps `header_name`. `Initial View:` is a reference and
|
||||
takes an `id_ref`.
|
||||
|
||||
**Group references.** A group reference (`[Coastal]`) is a name.
|
||||
**[Amendment]** It is the group's id. So is the group in a `collapsed:` line. The
|
||||
group definition (`> Coastal`) declares the id. The spellings are open question 10.
|
||||
**Group references.** A group reference (`[Coastal]`) is a name. **[Amendment]** It is
|
||||
the group's id, as is the group in a `collapsed:` line, and the group definition
|
||||
(`> Coastal`) declares a display and an id like any other declaration
|
||||
(`> Coastal(coastal)`).
|
||||
|
||||
**[Amendment] An axis line is tried before `format:`, and a format that reads as an
|
||||
axis line is pipe-quoted** (improvise-le4p). `view_entry` is an ordered choice, and
|
||||
@@ -330,20 +361,19 @@ lose its axis line to the view's number format, and the new-version arm puts
|
||||
that view also holds a category `format` on an axis.
|
||||
|
||||
**[Amendment] A formula line** is
|
||||
`- <target>(<formula id>) = <id-form expression> [<category id>]` (pending open
|
||||
question 1):
|
||||
`- <target>(<formula id>) = <id-form expression> [<category id>]`:
|
||||
- **The parenthesised id is the formula's `FormulaId`**, between the target and the
|
||||
`=`.
|
||||
- **`<target>` is the target item's id when the target is an item** (outside
|
||||
`_Measure`). For a `_Measure` target, which has no item, it is the target's
|
||||
display, held on the `Formula`.
|
||||
`=`. 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 4.
|
||||
- **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 id for a `_Measure` target.
|
||||
the formula's (and measure'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
|
||||
`- |Margin(%)|(margin) = …`. An item id never contains a parenthesis.
|
||||
`- |Margin(%)|(margin) = …`. An id never contains a parenthesis.
|
||||
|
||||
**`header_display` is guarded only by a lookahead**, which the generator skips
|
||||
(gotcha 34). So the generator may emit a display ending in something that parses as an
|
||||
@@ -352,7 +382,8 @@ property 1 must be stated over the parsed result, not over the generated text.
|
||||
|
||||
**The version line alone picks the regime** (clause 10).
|
||||
- **In an old-version file** a header is the whole line, a reference resolves against
|
||||
display names, and ids are assigned by slugify.
|
||||
display names, and ids are assigned by slugify. This resolution migrates a file
|
||||
whose names were its only identity; it is not a reference by a person.
|
||||
- **In a new-version file** declarations carry ids and a reference is an id.
|
||||
|
||||
**An old file naming an item it never declares** declares it at load. This happens
|
||||
@@ -384,7 +415,7 @@ out, because reassigning an id would silently change what its references point a
|
||||
**Import** (CSV, JSON) never parses ids out of dimension names. A header `Region(code)`
|
||||
is the display `Region(code)`, and its id is its slug. The writer pipe-quotes any
|
||||
display that would otherwise read as display plus id:
|
||||
`## Category: |Region(code)|(regioncode)`.
|
||||
`## Category: |Region(code)|(regioncode)` (**[Amendment]** `(region_code)`).
|
||||
|
||||
### 6. The wire: address by id, create by display, learn ids from projections
|
||||
|
||||
@@ -393,13 +424,17 @@ display that would otherwise read as display plus id:
|
||||
being created is a display, and the host slugifies it:
|
||||
- `AddCategory(display)` and `CreateView(display)`;
|
||||
- `AddItem { category: CategoryId, display }`;
|
||||
- `AddItemInGroup { category: CategoryId, display, group }`. Whether `group` is an
|
||||
existing group's id or a new group's display is open question 10 **[Amendment]**;
|
||||
- `AddItemInGroup { category: CategoryId, display, group }`. **[Amendment]** `group`
|
||||
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 client accepts a display name as a reference**, in its UI
|
||||
layer, and it converts the name to an id as early as it can. The wire never carries
|
||||
a display as a reference. Whether the old-version loader and headless `improvise
|
||||
cmd`/`script` are exceptions is open question 4.
|
||||
- **[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 5.
|
||||
- **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
|
||||
@@ -408,6 +443,7 @@ display that would otherwise read as display plus id:
|
||||
- **New operations carry ids:**
|
||||
- `RenameCategory { category, to: display }`;
|
||||
- `RenameItem { category, item, to: display }`;
|
||||
- **[Amendment]** a group rename by group id and new display;
|
||||
- `ReplaceFormula { id, was, raw }`;
|
||||
- `RenameRecordCoordinate`;
|
||||
- the removes (`RemoveCategory`, `RemoveItem`, `RemoveFormula`, `DeleteView`).
|
||||
@@ -415,8 +451,7 @@ display that would otherwise read as display plus id:
|
||||
including `SetCell { view, … }` and `ClearCell { view, … }`), one whole variant at a
|
||||
time.
|
||||
- **Projections carry id and display together:**
|
||||
- `CategoryRow { id, display, items: Vec<(ItemHandle, String)>, axis }` (pending open
|
||||
question 1);
|
||||
- `CategoryRow { id, display, items: Vec<(ItemHandle, String)>, axis }`;
|
||||
- `FormulaCache` rows `{ id, stored, display }`. `display` is what the panel shows:
|
||||
the target's display with its id in parentheses (`Profit (profit) = Revenue -
|
||||
Cost`), then the expression over display names, with an id wherever a display is
|
||||
@@ -425,15 +460,17 @@ display that would otherwise read as display plus id:
|
||||
would bind elsewhere. `stored` is the id-form text the file carries, and
|
||||
`ReplaceFormula`'s `was` is compared against it. So a rename of `Revenue` elsewhere
|
||||
changes what the panel shows, but does not refuse an edit of `Profit`;
|
||||
- `ViewSummary { id, display, … }`.
|
||||
- `ViewSummary { id, display, … }`;
|
||||
- **[Amendment]** the grid frame's group entries (`FrameGroup`) carry the group's id
|
||||
and display.
|
||||
|
||||
`AxisFrame` labels stay display strings. They gain ids when a consumer needs them.
|
||||
- **`ViewState` keys by id**: the cursors, `expanded_cats` and the view history
|
||||
(`view_back_stack` / `view_forward_stack`). **[Amendment]** So do `active_view`
|
||||
itself and `Workbook::active_view` (improvise-p003). A rename then orphans nothing.
|
||||
- **[Amendment] A `_Measure` formula-target row** (`ItemHandle::FormulaTarget`) is
|
||||
removed by `RemoveFormula` and renamed by a display rename of the formula's target
|
||||
(improvise-i0n7; pending open question 1).
|
||||
removed by `RemoveFormula` and renamed by a display rename of the measure
|
||||
(improvise-i0n7).
|
||||
- **[Amendment] CheckedBatch's created-record exemption is removed** (improvise-p003).
|
||||
It exempts a record that neither resolves nor is claimed, for frames like
|
||||
`[AddItem(East), SetCell(East)]`. A frame cannot address what it creates, so nothing
|
||||
@@ -460,7 +497,7 @@ Per-entity versions are not added.
|
||||
text pairs: the same function over different strings. **[Amendment]** The pairs are
|
||||
`(CategoryId, ItemHandle)` spelled as id text. Ids are unique across kinds, so the hash
|
||||
stays canonical. A record is the key minus `_measure`, so a `FormulaTarget` coordinate
|
||||
is never part of a record (pending open question 1).
|
||||
is never part of a record.
|
||||
|
||||
ADR 002's decision is unchanged in substance. Its prose ("sorts coordinates by
|
||||
category name", records spelled in names) is out of date once this lands, and the
|
||||
@@ -486,7 +523,7 @@ whole-line reading.
|
||||
| `## View: \|Default (default)\|(default)` | pipes literal | display `Default (default)`, id `default` |
|
||||
| `## View: Sales (copy)` | `Sales (copy)` | **display `Sales`, id `copy`** |
|
||||
| `## Category: Region(code)` | `Region(code)` | **display `Region`, id `code`** |
|
||||
| `## Category: Budget (2024)` | `Budget (2024)` | **display `Budget`, id `2024`** |
|
||||
| `## Category: Budget (2024)` | `Budget (2024)` | **display `Budget`, id `2024`** (**[Amendment]** refused: an id may not start with a digit) |
|
||||
| `## Category: Time Period` | `Time Period` | unchanged; id `time_period` generated |
|
||||
|
||||
The app itself writes such headers: `improvise import` on a CSV with headers like
|
||||
@@ -496,7 +533,8 @@ If old files got the rule, that category would load as display `Region`, id `cod
|
||||
Every reference in the file spells `Region(code)`, which resolves to neither, so the
|
||||
axis lines and data keys would not resolve, and nothing would be drawn. Because old
|
||||
files keep the whole-line reading, none of this happens. On the first save the writer
|
||||
pipe-quotes such a display: `## Category: |Region(code)|(regioncode)`.
|
||||
pipe-quotes such a display: `## Category: |Region(code)|(regioncode)`
|
||||
(**[Amendment]** `(region_code)`).
|
||||
|
||||
### 10. The file version alone picks the regime
|
||||
|
||||
@@ -517,10 +555,12 @@ pipe-quotes such a display: `## Category: |Region(code)|(regioncode)`.
|
||||
|
||||
1. **Remove legacy JSON** (improvise-tt22). Done, in PR #224.
|
||||
2. **Types:** the id newtypes over `Symbol`, the `display` fields, `FormulaId` and
|
||||
`ViewId`. **[Amendment]** Also `ItemHandle`, which the re-key needs for `CellKey`
|
||||
(pending open question 1).
|
||||
`ViewId`. **[Amendment]** Also `GroupId`, a display on `Group`, and `ItemHandle`,
|
||||
which the re-key needs for `CellKey`.
|
||||
3. **Re-key storage by id** (clause 2). This is the wide, mechanical step, with
|
||||
display→id resolution moved to every input edge.
|
||||
display→id resolution moved to every input edge. **[Amendment]** The virtual
|
||||
categories' keys move from `_Index`, `_Dim` and `_Measure` to `_index`, `_dim` and
|
||||
`_measure` here, in the model, on the wire, in `ViewState` and in the browser client.
|
||||
4. **Persistence:** the new literal (clause 10), clause 5's grammar, the loader and its
|
||||
refusals, slugify, the writer, and the round-trip properties with a formula-line
|
||||
strategy.
|
||||
@@ -556,22 +596,22 @@ answers for those.
|
||||
- **A typed name that was not in the captured mapping** is resolved against the
|
||||
document at commit, and refused, naming it, if it does not resolve.
|
||||
|
||||
**[Amendment] The client does the resolving:**
|
||||
**[Amendment] The client does the resolving, and ids always win:**
|
||||
- **The client holds the mapping**, as it holds `OpenedCell`. The TUI and the browser
|
||||
share one resolver in `improvise-protocol`.
|
||||
- **A name new to the edit** is resolved by the client against its id-bearing
|
||||
projection (clause 6), **ids first, then displays**. Ids are unambiguous, so they are
|
||||
preferred wherever there is ambiguity.
|
||||
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.
|
||||
- A name that resolves to nothing is refused, and the refusal names it.
|
||||
- Ids are matched exactly, with no case folding.
|
||||
- **The long-term design separates two layers.** A view-layer autocomplete turns
|
||||
typed display text into ids before commit, and the model layer refers to ids only.
|
||||
What becomes of the ids-first order once that exists is open question 3. This ADR
|
||||
does not specify the autocomplete.
|
||||
- **Typing prefers ids and previews displays.** A view-layer autocomplete turns typed
|
||||
text into ids before commit, preferring a match on an id, and shows the matched
|
||||
thing's display as a preview. The model layer refers to ids only. This ADR does not
|
||||
specify the autocomplete further.
|
||||
- **The wire carries only id-form text.** `ReplaceFormula.raw` and `AddFormula.raw` are
|
||||
in the file's id form, and the host never resolves a display. The host parses ids,
|
||||
and one that no longer exists is `Rejection::Gone`.
|
||||
@@ -579,8 +619,8 @@ answers for those.
|
||||
**[Amendment] What ids-first costs.** Suppose item A is displayed `East` with id `east`,
|
||||
and item B is displayed `east`, so slugify gave it `east-1`. Typing `east` names **A**.
|
||||
B is reached by typing `east-1`. A user who typed `east` meaning B is bound to A
|
||||
without being told. That is why the panel shows B as `east-1` and never as a bare
|
||||
`east`.
|
||||
without being told, unless the preview shows it. That is why the panel shows B as
|
||||
`east-1` and never as a bare `east`.
|
||||
|
||||
**Required regression test.**
|
||||
1. Open a formula editor on `Profit = Revenue - Cost`.
|
||||
@@ -659,219 +699,118 @@ three things:
|
||||
- The formula panel is a second rendering of formula text, so display names typed
|
||||
there must be resolved back to ids (by the client, under the amendment).
|
||||
- ADR 002's prose needs a wording update.
|
||||
- **[Amendment]** Scripts that name things by display break and must be rewritten
|
||||
over ids.
|
||||
|
||||
**Not addressed.** Per-entity versions, ids in `AxisFrame` and `CellId::Derived`, and
|
||||
(under the accepted text) group ids.
|
||||
**Not addressed.** Per-entity versions, ids in `AxisFrame` labels and
|
||||
`CellId::Derived`, and (under the accepted text) group ids.
|
||||
|
||||
## Open questions
|
||||
|
||||
These are open, and nothing above depends on an answer except where it says so.
|
||||
|
||||
### 1. Is a formula and the measure it derives one thing with one id, or two things with two ids?
|
||||
### 1. May a hand-written id carry the collision suffix?
|
||||
|
||||
**The question.** A formula is a derived measure. Take `Revenue` and `Cost`, stored
|
||||
measures with ids `revenue` and `cost`, and add `Profit = Revenue - Cost`. Profit holds
|
||||
no data: the formula computes its column. Two kinds of thing must name Profit by an
|
||||
id:
|
||||
- things that refer to the **measure**: another formula (`Margin = Profit / Revenue`),
|
||||
each Profit cell's `CellKey`, a `hidden:` line, and ADR 008's cursor anchor;
|
||||
- things that refer to the **formula**: `ReplaceFormula` and `RemoveFormula`.
|
||||
**The question.** A hand-written id obeys every syntax rule of a generated id
|
||||
(clause 4), and slugify turns `-` into `_`, so a slug never contains `-`. But a
|
||||
generated id that collided carries a `-<n>` suffix (`east-1`), and an empty slug
|
||||
becomes `<kind>-<n>` (`item-4`). Every save writes those ids into the file, so the
|
||||
next load reads them as hand-written.
|
||||
|
||||
Do they use the same id?
|
||||
|
||||
**Why it matters.** It decides the `CellKey` type (clause 2), the formula line's
|
||||
shape (clause 5), `RecordHash` (clause 8), the `_Measure` panel rows (clause 6) and
|
||||
what `.improv` must write.
|
||||
**Why it matters.** If a hand-written id may never contain `-`, the app refuses to load
|
||||
a file it saved itself.
|
||||
|
||||
**Options.**
|
||||
- **(a) One id.** The formula *is* the measure. Profit is `profit` everywhere, with
|
||||
its display on the formula, and the line is `- Profit(profit) = revenue - cost`.
|
||||
- A Profit cell's coordinate is `ItemHandle::FormulaTarget(profit)`.
|
||||
- The cost: a stored `Profit` column and a formula `Profit` (merged into one measure
|
||||
today by `measure_item_names`) would have two ids, and one must be chosen to key
|
||||
cells.
|
||||
- `RemoveFormula` removes the measure.
|
||||
- The rare dimension target (an item `tax` plus formula `tax-1`) works differently.
|
||||
- ADR 008 needs `ItemHandle`.
|
||||
- This is the smallest change from the text above.
|
||||
- **(b)** is (a) with the dimension line spelled `- Tax(tax-1)(tax) = … [customcat]`
|
||||
(display, formula id, item id), so that the target position always holds a display.
|
||||
- **(c) Two ids.** Profit is a `_Measure` item `profit`, and its formula is `profit-1`.
|
||||
The line is `- profit(profit-1) = revenue - cost`.
|
||||
- `CellKey` returns to `(CategoryId, ItemId)` and `ItemHandle` goes.
|
||||
- A same-named stored measure is simply the item the formula attaches to.
|
||||
- Measure and dimension targets work alike.
|
||||
- The cost: the file must newly write the item and its display (today a formula
|
||||
target is rebuilt from `## Formulas`).
|
||||
- `RemoveFormula` leaves an item with no formula and maybe no data.
|
||||
- ADR 009's `_Measure` fallback in `find_item_category` goes.
|
||||
- **(a)** The syntax of an id is a slug, optionally followed by `-` and a number:
|
||||
`[a-z][a-z0-9]*(_[a-z0-9]+)*` then optionally `-[0-9]+` (which also covers
|
||||
`<kind>-<n>`). A `-` anywhere else is refused.
|
||||
- **(b)** Change the suffix to avoid `-`, for example `east_1`. That collides with a
|
||||
display such as `East 1`, which also slugs to `east_1`, so the counter must advance
|
||||
past it.
|
||||
|
||||
Also to settle: whether the writer keeps omitting the `[_measure]` tag on a `_Measure`
|
||||
formula line, as today's writer does.
|
||||
**Author's recommendation:** (a). It keeps the counter's suffix visibly distinct from
|
||||
anything a display can slug to.
|
||||
|
||||
**Recommendation:** none. Both (a) and (c) model a derived measure, and the choice is a
|
||||
product judgement about whether a formula and its column are one thing.
|
||||
### 2. Does "alphanumeric" mean ASCII?
|
||||
|
||||
### 2. What may a formula refer to?
|
||||
|
||||
**The question.** Formulas may refer to things in a typed context. An example is a
|
||||
function counting how often a letter appears in a category value. Today a formula
|
||||
reference (`Expr::Ref`) names an item or a formula target. Three things are open:
|
||||
- (i) may a bare id in formula text also name a category, a view or a group, with
|
||||
its kind given by where it appears?
|
||||
- (ii) does a text function read an item's display (`Café`) or its id (`caf`)? They
|
||||
differ, because slugify folds case and strips;
|
||||
- (iii) must this ADR specify that, or only keep ids unique so it can be added later?
|
||||
|
||||
**Why it matters.** It decides whether the formula grammar and evaluator change with
|
||||
this ADR.
|
||||
|
||||
**Author's recommendation:** (iii). The formula language has no string function
|
||||
today, and global uniqueness already makes a reference to any kind unambiguous.
|
||||
|
||||
### 3. Does ids-first matching outlive the interim resolver?
|
||||
|
||||
**The question.** Clause 12's resolver matches typed text against ids before displays,
|
||||
and ids are preferred wherever there is ambiguity. Once a view-layer autocomplete
|
||||
turns display text into ids, does the preference carry into it?
|
||||
**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 `-`).
|
||||
|
||||
**Options.**
|
||||
- **(a) Yes.** Wherever typed text could name both an id and a display, the id wins,
|
||||
including in the autocomplete's ranking.
|
||||
- **(b) No.** Once the autocomplete exists, nothing is matched at commit, so the rule
|
||||
ends with the interim resolver.
|
||||
- **(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.
|
||||
|
||||
**Recommendation:** none. It matters only once an autocomplete is designed.
|
||||
**Why it matters.** It decides every id generated from a non-English display.
|
||||
|
||||
### 4. May anything other than a client resolve a display name?
|
||||
**Author's recommendation:** (a): it is the simplest reading of "non-alphanumeric
|
||||
characters are replaced", and it needs no table.
|
||||
|
||||
**The question.** Only a client accepts a display as a reference (clause 6). Two
|
||||
places would resolve displays outside one:
|
||||
- the **old-version loader**, which resolves an old file's references by display
|
||||
(clause 5), because in an old file the name is the only identity;
|
||||
- **headless `improvise cmd` and `improvise script`**, which run command lines naming
|
||||
things by display, with no client attached.
|
||||
### 3. How does a file record the virtual categories, if virtual ids are never written?
|
||||
|
||||
**Why it matters.** If neither is allowed, old files cannot be migrated and the
|
||||
headless CLI needs a client layer of its own.
|
||||
**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`.
|
||||
|
||||
**Author's recommendation:** allow both. The loader is migrating a file, not a person
|
||||
referring to something. The CLI is the UI layer of its own invocation and converts at
|
||||
its edge, as a client does.
|
||||
|
||||
### 5. Does the `_` collapse come after the strip?
|
||||
|
||||
**The question.** Clause 4 collapses runs of `_` (step 4) after stripping characters
|
||||
outside the alphabet (step 3).
|
||||
|
||||
**Why it matters.** `Time \u{200B} Period` (a zero-width space between two spaces)
|
||||
gives `time_period` only if the collapse comes after the strip. Before it, the
|
||||
zero-width character still separates the two `_`, and the result is `time__period`.
|
||||
|
||||
**Options:** (a) after the strip, as written; (b) before it.
|
||||
**Author's recommendation:** (a).
|
||||
|
||||
### 6. Which entities share the reserved leading `_`?
|
||||
|
||||
**The question.** A leading `_` is reserved for virtual categories, measures and other
|
||||
entities.
|
||||
- (i) Does "measures" mean virtual measures (none exists today), the `_measure`
|
||||
category, or something else?
|
||||
- (ii) Which "other entities" are covered? In particular, are the virtual views,
|
||||
`_records` and `_drill` (clause 1), among them?
|
||||
|
||||
**Why it matters.** It fixes `VIRTUAL_IDS` and what `claim` refuses.
|
||||
|
||||
**Author's recommendation:** (ii) yes, as clause 1 is written. Otherwise `_Drill`
|
||||
slugs to `drill` and can collide with a user's view.
|
||||
|
||||
### 7. Are records mode's positions under `_Index` and `_Dim` ids?
|
||||
|
||||
**The question.** Records mode gives each row a synthetic coordinate under `_Index`,
|
||||
and each column one under `_Dim` (a column index, gotcha 1). They are never persisted
|
||||
and never typed.
|
||||
**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.
|
||||
|
||||
**Options.**
|
||||
- **(a)** Outside the id namespace: a position `0` under `_index` does not collide
|
||||
with a user's item `0`.
|
||||
- **(b)** Inside it, spelled so that it cannot collide (for example `_index-0`), under
|
||||
the reservation of a leading `_`.
|
||||
- **(c)** Something else.
|
||||
- **(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.
|
||||
|
||||
**Why it matters.** Under (b), uniqueness is checked for synthetic coordinates, and
|
||||
`claim`'s exact match on reserved ids must change.
|
||||
**Author's recommendation:** (a). The file needs these facts, and a keyword says so
|
||||
without turning a virtual id into file content.
|
||||
|
||||
**Recommendation:** none.
|
||||
### 4. How does a dimension formula's line spell its target, and is the `[_measure]` tag written?
|
||||
|
||||
### 8. How do the virtual categories move to their lower-case ids?
|
||||
**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.** Today `_Index`, `_Dim` and `_Measure` are keys in the model, on the
|
||||
wire, in `ViewState` and in `.improv` files. For example, `web/demo.improv` has
|
||||
`_Measure: column`, `_Index: none`, `## Category: _Measure` and `_Measure=Revenue`.
|
||||
Under clause 1 they become `_index`, `_dim` and `_measure`, with the displays
|
||||
unchanged. The proposed migration:
|
||||
- an old file's `_Measure` resolves to `_measure` through its display at load, like
|
||||
any name, and the first save writes `_measure: column` and `_measure=revenue`;
|
||||
- a new-version file must spell the id, and `_Measure` there is refused (ids are
|
||||
lower case);
|
||||
- the header becomes `## Category: _Measure(_measure)`;
|
||||
- every string literal naming them in code, on the wire and in the browser client
|
||||
moves in the re-key step.
|
||||
Today's writer omits the category tag for a `_Measure` formula. Should the new writer
|
||||
keep omitting `[_measure]`?
|
||||
|
||||
**Why it matters.** Every shipped document and much test code spells these names.
|
||||
**Why it matters.** It fixes the formula-line grammar and its generator strategy.
|
||||
|
||||
**Author's recommendation:** the migration above.
|
||||
**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 3).
|
||||
|
||||
### 9. Does a group also carry a display name?
|
||||
### 5. Which headless entry points are scripts?
|
||||
|
||||
**The question.** A group's identifier is an id, not a friendly name (clause 1). Does
|
||||
a group also have a separate display, as categories, items and views do?
|
||||
**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.**
|
||||
- If it does not, the grid and panels show the id: `Coastal East` would appear as
|
||||
`coastal_east`.
|
||||
- If it does, groups need a display rename like everything else, and the file
|
||||
declares both.
|
||||
- The code is already moving: PR #336 (improvise-cq1q.12, open) adds a private
|
||||
display to `Group`, beside its key.
|
||||
**Why it matters.** It decides whether `improvise cmd 'add-item Region East'`-style
|
||||
invocations that name existing things by display keep working.
|
||||
|
||||
**Options:** (a) yes, a display beside the id; (b) no, the id is all a group has.
|
||||
**Author's recommendation:** (a), for consistency with every other id-bearing kind and
|
||||
so that users see what they typed.
|
||||
**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.
|
||||
|
||||
### 10. How do groups carry ids?
|
||||
|
||||
**The question.** A group's identifier is an id. Not yet decided:
|
||||
- (i) **Namespace.** Is a group id unique in the whole document, like every other id,
|
||||
or only within its category?
|
||||
- (ii) **Generation.** Is it the slug of a display (if question 9 gives it one), with
|
||||
`group` as an empty slug's `<kind>` and the shared counter for collisions?
|
||||
- (iii) **The file.** For example a definition `> Coastal(coastal)`, a membership
|
||||
`- Coastal_East(coastal_east)[coastal]`, and a parent group (group hierarchies,
|
||||
improvise-felo.6) named by id. Does a `collapsed:` line hold the group id with or
|
||||
without its category id?
|
||||
- (iv) **The wire.** Does `AddItemInGroup`'s `group` name an existing group by id,
|
||||
create one by display, or either? Is there a `RenameGroup { group, to }`?
|
||||
- (v) **The frame.** `AxisFrame`'s `FrameGroup` carries a group name today. What does
|
||||
it carry?
|
||||
|
||||
**Why it matters.** Clauses 2, 5, 6 and 7 cannot be implemented for groups without
|
||||
these answers.
|
||||
|
||||
**Author's recommendation:** (i) document-wide, as every other id is; (ii) slugify,
|
||||
like every generated id. No recommendation on (iii)–(v).
|
||||
|
||||
### 11. What may a hand-written id look like at its ends, and do slugify's trims repeat?
|
||||
|
||||
**The question.** No id may be `-`, and no id begins or ends with `-`.
|
||||
- (i) Is a hand-written id that begins or ends with `-` refused at load, as one
|
||||
beginning with `_` is?
|
||||
- (ii) Slugify drops a trailing `_`. Is a hand-written id ending in `_` refused too?
|
||||
- (iii) Clause 4's step 5 repeats until neither end holds `_` or `-`, so `-_x` → `x`.
|
||||
One pass of each would leave `_x`, which is reserved, or `x_`. Is repeating right?
|
||||
|
||||
**Why it matters.** It decides what `IdAllocator::claim` refuses. Today it accepts
|
||||
`-`, `-x` and `x-`.
|
||||
|
||||
**Author's recommendation:** (i) yes; (ii) yes, so that a hand-written id obeys the
|
||||
same shape as a generated one; (iii) repeat.
|
||||
**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.
|
||||
|
||||
@@ -1715,11 +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:
|
||||
- each whitespace character becomes `_`, not only U+0020;
|
||||
- runs of `_` collapse after the strip, so `a_ b` and `a__b` both slug to `a_b`
|
||||
and `Time \u{200B} Period` to `time_period`;
|
||||
- leading and trailing `_` and `-` are removed, so `East ` → `east`, `-East-` →
|
||||
`east`, and `-` alone takes `<kind>-<n>`.
|
||||
- every non-alphanumeric character becomes `_` (not only U+0020, and `-` too),
|
||||
so `Q1-2024` slugs to `q1_2024` and `Region(code)` to `region_code`;
|
||||
- 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`;
|
||||
- `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.
|
||||
|
||||
This section, `slugify`, and the tests
|
||||
`slugify_collapses_a_run_of_spaces_to_one_underscore` (its `east_` case) and
|
||||
@@ -1750,8 +1751,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` and `x-`. ADR 007's open question 11 asks
|
||||
whether a hand-written id like that must be refused. It never renames.
|
||||
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.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user