Configuration Tab
Note
Status: implemented. The Config tab shows the project's dialogue.toml beside the speakers
it configures, lets a served session edit and save it with schema autocompletion, and offers to
create one when the project has none.
Table of contents
- Goal and scope
- Ubiquitous language
- Where it sits
- View
- Edit
- Autocompletion
- Create
- Error and boundary cases
- Testability
- Open decisions
Goal and scope
The compiler reads a dialogue.toml to seed configured speakers, but a reader cannot otherwise
see which configuration a compile applied, or whether one was found. The Config tab answers both,
and closes the loop for writing one:
| Mode | What the tab does |
|---|---|
| View | The raw TOML on the left, the resolved configured speakers on the right, and the config path in the status bar. |
| Edit (served) | The TOML is a real editor; saving it writes the file and recompiles the script. |
| Edit, autocompletion | The editor suggests the [[speakers]] header, its keys, and the reserved tag names. |
| Edit, no config | A Create dialogue.toml button writes a starter file and opens it for editing. |
Out of scope: choosing another name or folder for a new file, and deleting or renaming one.
Ubiquitous language
| Term | Meaning |
|---|---|
| Applied configuration | What a compile applied: an optional configuration file plus the resolved CompilerOptions (AppliedConfiguration). |
| Configuration file | A found dialogue.toml: its path and raw source, always present together (ConfigurationFile). |
| Configured speaker | A speaker supplied by configuration: a name, an optional id, and custom or reserved tags. |
| No-config state | The tab when the compile found no dialogue.toml, so the compiler ran on its built-in defaults. |
| Config document | The Config tab's editable buffer. Document alone always means the dialogue script. |
| Save target | Which file a save writes: document (the script) or config. |
| Serve root | The folder the server hosts; a created dialogue.toml is written there. |
| Adopt | A running session starting to apply a config file it did not start with. |
The word source is overloaded, so it is always qualified: report.source is the script,
report.configuration.file.source is the TOML.
Where it sits
flowchart LR
subgraph cli["CLI edge"]
PC["ProjectConfiguration.ResolveApplied<br/>discover + read + parse"] --> AC["AppliedConfiguration<br/>{ File?, Options }"]
end
AC --> VIZ["CompilationVisualizer"]
VIZ --> PAY["report payload<br/>configuration: { file?, speakers, reservedTags }"]
PAY --> TAB["Config tab (createConfigView)"]
TAB -->|"Save, target: config"| SES["LiveSession"]
SES -->|"re-parse TOML, rebuild visualizer, recompile"| PAY
Configuration is resolved once, at the CLI edge, and travels as a document into the visualizer.
The types live in DialogueDown.Visualization, not the core, because they carry a file path and
raw TOML — tooling concerns the engine-agnostic core does not have.
View
D1 — The report carries the applied configuration as a document
AppliedConfiguration { ConfigurationFile? File, CompilerOptions Options } has two levels of
optionality, and each names one fact:
| Payload | Meaning | Tab |
|---|---|---|
No configuration field |
A bare library render, with no configuration context | No Config tab |
configuration without file (UsesDefaultConfiguration) |
No dialogue.toml was found |
The no-config state |
configuration.file present (IsConfiguredFromFile) |
A real file: path and text together | TOML and speakers |
Call sites read the named predicates — IsConfiguredFromFile in .NET, isConfiguredFromFile in
model.ts — rather than testing the nullable field.
D2 — The first tab, with a gear, but the report opens on Source
The tab sits before Source and carries a Feather settings gear. A reader who wants the script is
undisturbed; the configuration is one click away.
D3 — Its own split, sharing the Source tab's machinery
The tab reuses initSplitDivider, initCollapsiblePanel, and the maximize control, but owns its
classes (.config-view, .config-source, .config-divider, .config-side) and its
--config-split variable. Config and Source are both in the DOM at once, so shared .source-*
selectors would match two panes.
D4 — TOML highlighting through the legacy mode
@codemirror/legacy-modes/mode/toml, wrapped in StreamLanguage, highlights the file. There is no
official Lezer TOML grammar.
D5 — The speakers table is a small dedicated renderer
The right pane is a Name · Id · Tags table drawn by config-view.ts with the shared
.semantic-table styling, not a createTablePanel call. Tags use the report's shared
tag capsules. Every value cell — the name, the
@id, and each capsule — copies on click, confirmed by the shared toast (showToast).
D6 — Two paths in the status bar
path-display.ts shows the script's path and the config's, each led by an icon (a document, a
gear), shortening the directory with an ellipsis and copying the full path on click. With no file, the config path
reads No config file.
D7 — No config file is an ordinary state
Most scripts need no dialogue.toml, so its absence is explained rather than reported as an error:
the left pane says the script compiled with the built-in defaults and what a config file would add;
the speakers table says No configured speakers yet.
Edit
In Edit mode the TOML is editable; the rest of the edit loop is the Source tab's, shared rather than duplicated. See Live Edit and Autosave for the save protocol itself.
D8 — One compile, two editable inputs
A served session compiles one thing: the script. The config is a second input to that compile, not a second thing to compile. So there is still one report, one View⇄Edit toggle, one save surface, and one recompile; only the save target and the dirty buffer multiply.
D9 — One document is dirty at a time
The navigation lock that keeps a dirty Source tab from being left also locks a dirty Config tab. At most one editable document therefore has unsaved edits, so a config save never has to reconcile two buffers: the server recompiles the script from disk with the new configuration.
D10 — A save carries its target
POST /api/save takes target: "config" for the Config tab (default document). The client picks
the target from the dirty document. A config save carries the same expected baseline and
validation policy as a script save, so an external change is a conflict rather than an overwrite.
D11 — Saving re-parses the TOML and rebuilds the visualizer
LiveSession parses the saved text with the same TomlConfigurationLoader the CLI uses, rebuilds
its CompilationVisualizer with the new options, and recompiles. The response is the same report
payload a script save returns, so the client applies it the same way and refreshes the speakers.
An invalid TOML save persists the text but keeps the last valid report (saved-invalid); a save
that requires validity (Auto, or leaving the tab) does not write it (invalid-auto).
D12 — The speakers refresh on save, not per keystroke
Resolving TOML into speakers is .NET logic, so while the buffer is dirty the speakers pane shows Unsaved changes — save to refresh the speakers. The hint clears when the recompiled report arrives.
D13 — External edits hot-reload
The server watches dialogue.toml like the script; an external change pushes a reload-config
event that refreshes the tab.
Autocompletion
config-completions.ts adds schema completion to the editable Config editor:
[[speakers]] # offered when a line starts with "["
name = "Guide" # name, id, tags, and the reserved tag names
id = "guide" # are offered at a key position under [[speakers]]
tags = ["wise"]
D14 — Schema completion, not a document scan
The Source editor completes from symbols scanned out of the script. The config's vocabulary is a
small fixed schema, so its two sources (tableHeaderCompletions, speakerKeyCompletions) return
constant lists filtered by the typed prefix.
D15 — Reserved tag names come from the compiler
The structural keys ([[speakers]], name, id, tags) are a constant in the client. The
reserved tag names are a closed set the loader enforces, so they travel in the payload
(configuration.reservedTags, projected from ReservedTagNames.Known) and cannot drift.
D16 — Context decides; Edit only
A key position is the start of a line, before any =, under a [[speakers]] header. Value
positions, comments, and strings get nothing. The completion lives in the Edit-only compartment
with Tab as a second accept key, as in the Source editor. closeBrackets there skips [, so a
typed or accepted [[speakers]] header is not left with a stray ].
Create
D17 — Create in place, from the no-config state
In Edit, the no-config pane shows Create dialogue.toml; in View it shows Switch to Edit to
add one. The static export shows neither, because it has no server to write with. The button
posts to POST /api/create-config and the reader stays in the report.
D18 — The server owns the path
The route takes no path. The server writes <serve root>/dialogue.toml
(ConfigurationFile.DefaultName), which is at or above the script, so the next compile's upward
discovery finds it — and no request value can reach the filesystem.
D19 — A commented starter template
The new file is a commented [[speakers]] example with name, id, tags, and ##default. It
compiles to the same defaults until the writer uncomments it.
D20 — The session adopts the file; the page reloads onto Config
LiveSession.CreateConfig sets the session's config path, rebuilds the visualizer, starts the
config watcher, and returns the recompiled payload. The client then reloads, with a one-shot
sessionStorage flag (config-create.ts) so the reloaded page opens on the Config tab instead of
Source.
D21 — An existing file is adopted, never overwritten
The create is exclusive, so a file that appeared first is never clobbered:
| Found on disk | Outcome | HTTP |
|---|---|---|
| Nothing | Starter written and adopted (Created) |
200 |
| The starter template (a retry) | Adopted as is (Adopted) |
200 |
| A different file | Adopted as is (AdoptedExisting) |
200 |
| The session's adopted file, since changed | Nothing written (Conflict) |
409 |
| Serve root not writable | Write failure | 400 |
Error and boundary cases
| Case | Behavior |
|---|---|
Config file present but has no [[speakers]] |
The TOML shows; the speakers table shows its empty note. |
| Malformed TOML at startup | The loader throws before a report is built. |
| Malformed TOML saved from the tab | saved-invalid or invalid-auto, per D11. |
| Served session with no script path | A configuration context exists, so the tab shows, in the no-config state. |
| No reserved tags in the payload | Reserved-tag suggestions are empty; structural keys still complete. |
Testability
| Level | Covers |
|---|---|
.NET — ProjectConfiguration.ResolveApplied |
An explicit --config, a discovered file, and none found. |
| .NET — payload | configuration present or omitted; file and reservedTags projected. |
.NET — LiveSession |
A config save re-parses and recompiles; invalid TOML outcomes; CreateConfig outcomes; the route's 409 and 400. |
Vitest — config-view |
Read-only and editable modes; chip classes; no-config pane with the button in Edit and the hint in View. |
Vitest — config-completions |
Each source at a header, a key position, a value, a comment, and another table. |
Vitest — config-create |
Success reloads and flags the tab; a 409 surfaces its message. |
| Playwright | The tab is first with a gear and the report opens on Source; edit, save, and the speakers refresh; the nav lock; autocompletion; creating a file lands on the editable tab. |
Open decisions
- Copyable names. The Config table copies a speaker's name on click. The report's cell rule says a name is prose and does not copy, and the Semantic Model and Playbook speaker tables follow it. One of the two should change.
- The
Idheader. The Config table heads its id columnId; the other two speaker tables head it@id.