Skip to content

Experiment: Replace CLI UX with much broader TUI app/IDE#5689

Draft
StachuDotNet wants to merge 151 commits into
darklang:mainfrom
StachuDotNet:cli-ux-redux
Draft

Experiment: Replace CLI UX with much broader TUI app/IDE#5689
StachuDotNet wants to merge 151 commits into
darklang:mainfrom
StachuDotNet:cli-ux-redux

Conversation

@StachuDotNet

@StachuDotNet StachuDotNet commented Jul 19, 2026

Copy link
Copy Markdown
Member

Very WIP, not sure where this will go.

Instead of dropping you at a bare prompt where you type a command and read the result, this evolves the default
dark CLI experience into a framed, keyboard-driven IDE over Dark Matter — the synced package space you
browse, read, author, version, and sync. dark with no args opens the workbench; the classic prompt is still
there for anyone who wants it (DARK_CLASSIC=1 at launch), and every arg'd command (dark status, dark eval, …) is untouched. Branch cli-ux-redux.

The old UX was super bare-bones — a prompt, a command, a wall of printed output, and you rebuild the picture in
your head across ls / view / status / commits / deps. This expands that into separate views and
panes
over the same Matter: where you are, what's changed, what it depends on, what it looks like — on one
screen, and you stay in it through the whole author → edit → review → commit → sync loop. I'm less attached to
any single view than to the shape: a persistent home vs. a prompt.


The choices most worth reacting to: commit to it, don't hedge — no args opens the workbench, no chooser,
and the workbench is the experience: Esc (or q) just leaves, straight to the shell — there's no
classic-prompt fallback to back out into. The old world doesn't live behind an exit, it lives in the frame:
: is an inline command bar that runs any classic command (status, ls, agent ask, …) and shows its
output in-frame, so you never leave to reach it. And the noun under all of it is Dark Matter, which syncs
the Matter view (below) is the home for its state.


The frame

Every view shares one frame: a tab bar (14 views, active one marked ), a breadcrumb with the current
path on the left and branch + sync status on the right, and a footer that only advertises the keys that
actually do something in the current view. All fourteen, real, from a running dark:

  ◆ Home   Matter   Inspect   Edit   Changes   History   Conflicts   Devices   AI   Runs   Apps   Values   Docs   SCM
  Home                                                              branch: main  ✓ synced

Navigation is uniform: 1-9 jump to a view, [ / ] cycle, ` opens a picker for all fourteen (the
number keys only reach the first nine), ↑↓ or j/k move, tab toggles the focused pane, / searches
(Enter jumps to a hit), ? is a keymap overlay, : is the command bar. The footer re-labels itself per view,
so the keys you see are the keys that work.


Views

A framed home over the same Matter, one angle per view. Most are a list + live preview split: the list on
the left, and whatever the selection is — source, ops, a diff, a rendered view — on the right.

Home — a dashboard: where you are, what's changed, who you are.

  Welcome back — you're on main
    ✓ working tree clean
    1 commit on this branch
    last commit: Init: packages loaded from disk
    3 top-level owners
    logged in as Stachu

Matter — browse the Matter tree; the selection's source is syntax-highlighted on the right. Icons carry the
kind: 🗂 module, # type, value, fn.

  /Darklang.Stdlib.List (module)
  ┌─ Tree ──────────────────────┐┌─ Inspect ──────────────────────────────┐
  │  🗂 SortByComparatorHelpers/ ││ /// Calls fn on every val in list, …    │
  │  # ChunkBySizeError          ││ let map<'a, 'b> (list: List<'a>)         │
  │  • empty                     ││     (fn: 'a -> 'b): List<'b> =           │
  │> ⚡ map                      ││   Stdlib.List.fold list [] (fun acc e -> │
  │  ⚡ filter                   ││     Stdlib.List.push acc (fn e))         │
  │  ⚡ fold                     ││   |> Stdlib.List.reverse                 │
  └──────────────────────────────┘└──────────────────────────────────────────┘
  ↑↓ move · → in · ⏎ open · n/t/v new · e edit · r rename · d deps

Inspect — with nothing drilled in, a live Package Statistics overview: totals and a per-module bar
chart, sortable and filterable.

  ┌─ Overview ──────────────────────────────────────────────┐
  │  Package Statistics  —  7 top-level modules             │
  │   629 fns    182 types    54 values    76 modules       │
  │  ─────────────────────────────────────────────────────  │
  │   MODULE                  FNS  TYPES  VALS  MODS  FNS    │
  │   Darklang.Stdlib         247    42    18    23  ██████  │
  │   Darklang.LanguageTools  156    67     8    19  ████░░  │
  │   Darklang.Cli             89    31    12    15  ███░░░  │
  │   Darklang.SCM             43    14     5     7  ██░░░░  │
  │  ─────────────────────────────────────────────────────  │
  │   [q] quit   [s] sort by column   [/] filter            │
  └──────────────────────────────────────────────────────────┘

Edit — author anything and apply it at once, no navigating first. Start a draft and you get a full editor
prefilled with a module <you>.Drafts header; write the module + declaration, ^s parses the whole thing and
applies it to the tree. Change the module line to save anywhere.

  ┌─ Edit ──────────────────────────────────────────────────┐
  │ Edit anything, apply at once.                           │
  │   n / ⏎   start a draft — a full editor prefilled with: │
  │               module <you>.Drafts                       │
  │               let example (x: Int): Int = x             │
  │           write the module + declaration, ^s applies it │
  │ Or from Matter: n/t/v new · e edit in place · r rename. │
  └──────────────────────────────────────────────────────────┘

Changes / History — your WIP, and the op log. History is a list of commits with the selected commit's detail
(and its ops, on Enter) on the right.

  ┌─ History · 1 ────────────────────────┐┌────────────────────────────┐
  │> • 9fa317f3  Init: packages loaded    ││ 9fa317f3…                   │
  │              from disk  (10908 ops)   ││ Init: packages loaded       │
  │                                       ││ 10908 ops  ·  ⏎ to view them │
  └───────────────────────────────────────┘└──────────────────────────────┘

Conflicts — incoming conflicts, resolvable right here: keep ours, keep theirs, or acknowledge. In sync, it
says so.

  ┌─ Conflicts ─────────────────────────────────────────────┐
  │ ✓ nothing to resolve — in sync                          │
  └──────────────────────────────────────────────────────────┘
  ↑↓ move · o keep ours · t keep theirs · a acknowledge

AI — a thread board: each agent thread with status/branch, and the selected thread's detail (cost, tokens,
what it's editing) on the right. a asks the agent a new prompt. (Board is demo data for now.)

  ┌─ AI · 6 ─────────────────────────────────┐┌─ detail ─────────────┐
  │> • [RUNNING]  Add binary serialization    ││ topic:  Add binary    │
  │  • [REVIEW]   Refactor HTTP error paths   ││ status: running       │
  │  • [RUNNING]  Write tests for pkg manager ││ branch: ai/sync       │
  │  • [REVIEW]   Design new DB migration     ││ cost: $4.82 · 128k tok │
  │  • [RUNNING]  Fix canvas render perf      ││ · 12m                 │
  │  • [DONE]     Update stdlib List docs     ││ editing sync/serialize │
  └───────────────────────────────────────────┘└────────────────────────┘

Apps & Views — the app registry + registered views; move onto a view and its render is captured live into
the preview pane, so you quick-peek a pretty view just by tabbing.

  ┌─ Apps & Views · 11 ──────────────┐┌─ Preview ─────────────┐
  │> • AI Chats                      ││  dark ai chats — 6     │
  │  • Package Stats                 ││  ID   STATUS   TOPIC   │
  │  • UI Components                 ││  #1   RUNNING  Add …   │
  │  • Heartbeat  — Demo daemon …    ││                        │
  └──────────────────────────────────┘└────────────────────────┘

Values — every package value (447 of them), browsable, with the value's definition on the right.

  ┌─ Values · 447 ────────────────────────┐┌──────────────────────┐
  │> • …UI.Core.…BoxChars.bottomLeft     ▐││ val bottomLeft = ""  │
  │  • …UI.Core.…BoxChars.bottomRight     ││                       │
  │  • …UI.Core.…BoxChars.cross           ││                       │
  │  • …UI.Core.…BoxChars.doubleHorizontal││                       │
  └────────────────────────────────────────┘└───────────────────────┘

Docs — the doc set (for-ai, syntax, types, …), each rendered as markdown in the preview. Some are
live (the Stdlib overview computes its counts).

  ┌─ Docs · 18 ──────────────────────────┐┌─ for-ai ──────────────┐
  │> • for-ai  —  AI agent guide          ││ # Darklang AI Quick    │
  │  • syntax  —  Language syntax         ││ ## Darklang is Different│
  │  • types   —  Type system overview    ││ LIVE PROGRAMMING ENV…  │
  │  • pattern-matching — Match exprs      ││ - fn to CREATE         │
  │  • stdlib  —  Live: Stdlib counts      ││ - eval to TEST         │
  └────────────────────────────────────────┘└─────────────────────────┘

SCM — one SCM-centric home over Dark Matter: a status header (branch → parent, WIP, commits, conflicts,
peers — the always-true summary) over the change set, with every version-control action in the footer.

  SCM
  branch: main   →   (root)
  ✓ clean     1 commit     no conflicts     no peers
  ┌─ Changes ───────────────────────────────────────────────┐
  │ ✓ working tree clean — nothing to commit                │
  └──────────────────────────────────────────────────────────┘
  ↑↓ move · c commit · x discard · b branch · s switch · m merge · r rebase

Devices / Runs — the tailnet mesh, and recorded traces. Both report their real state honestly rather than
faking data:

  Devices:  tailnet unavailable — tailscale not running or not installed
  Runs:     no runs yet — eval, run a script, or serve to record traces

Views live in the tree

The nicer version of "peek at a view" isn't a separate registry — it's a renderable value lighting up where it
lives
in the Matter tree. Cursor onto a render fn anywhere in the tree and its output renders live in the
preview, instead of source. Here, Apps.Views.AiChats.render selected in the tree, its board rendered live on
the right:

  /Darklang.Cli.Apps.Views.AiChats
  ┌─ Tree ────────────┐┌─ Inspect (live render) ─────────────────────┐
  │  🗂 …              ││  dark ai chats — 6 threads · 3 running · 2  │
  │> ⚡ render         ││  ID    STATUS    TOPIC             BRANCH   │
  │                   ││  #1   RUNNING  Add binary serial…  ai/sync  │
  │                   ││       ╰─ editing sync/serialize.dark        │
  │                   ││  #2   REVIEW   Refactor HTTP err…  ai/errs  │
  │                   ││       ╰─ ⚠ Awaiting review: 3 questions     │
  └────────────────────┘└──────────────────────────────────────────────┘

Overlays

Three modal overlays, reachable from any view.

? — a keymap, grouped and self-labeling per the keys that actually work:

  ┌─ Keys · any key closes ─────────────────────────────────┐
  │ Global     : cmd   / search   ` views   ? help   esc/q  │
  │ Views      1-9 jump   [ ] cycle                         │
  │ Move       ↑↓ or j/k   g/G top/bottom   tab focus pane  │
  │ Tree       →/l in   ←/h up   ⏎ open   n/t/v new   e edit │
  │ Changes    c commit   x discard                         │
  │ SCM        c commit · x discard · b branch · m merge …  │
  └──────────────────────────────────────────────────────────┘

` — a jumpable picker for all fourteen views (the number keys reach only the first nine):

  ┌─ Go to view ────────┐
  │    1  Home          │
  │ >  2  Matter  ◆     │
  │    3  Inspect       │
  │    4  Edit          │
  │    …                │
  │   14  SCM           │
  └─────────────────────┘

: — the command bar. Runs any classic command and shows its output in-frame, so the whole old CLI is
reachable without leaving the workbench:

  $ status
  On branch main
  No uncommitted changes.

The loop, end to end: navigate, read source or the op log, author or edit a fn/type/value (n/t/v + ^s,
from anywhere — no depth trap), rename (r, a real one-op rename), review the diff, commit, branch, merge,
rebase, resolve conflicts — without leaving the screen.


Reusable UI (generic — not workbench-specific)

Each is a small component with a typed input and a render. Worth a look on their own; any Dark TUI can mount
them.

UI.Box — a focus-aware bordered/titled pane. draw region title focused -> innerRegion.

┌─ Title ───────────────┐
│ inner content region  │
└───────────────────────┘

UI.SplitPane — two bordered panes; render region orientation firstPct focus titleA renderA titleB renderB. Composes Box; the focused pane gets the bright border.

┌─ Tree ──────────┐┌─ Inspect ───────┐
│> 🗂 Stdlib/      ││ type Expr =     │
│  ⚡ parse        ││   | EInt of Int │
└─────────────────┘└─────────────────┘

UI.ListView — a scrollable/selectable list body: render region (displayLines: List<String>) (selected: Int). Owns the bottom-anchored viewport + the overflow scrollbar; the caller styles its own rows.

> item one                    ▐   <- selection (cyan) + scrollbar thumb
  item two
  item three

UI.Editor — a pure multiline text buffer: Buf { lines: List<String>; row: Int; col: Int } with
insertText / newline / backspace / motion. Eval-testable (no I/O).

let newFunction (x: Int): Int =
  x█                                <- reverse-video cursor at (row, col)

UI.Layout — the region model (Region, printAt, vstack/hstack, distributeRows/Cols) plus
truncateVisible (s: String) (maxVisible: Int) : String — an ANSI-aware truncate that cuts a highlighted line
to a visible width without slicing through a color code. This is what lets highlighted source and captured
command output fit any pane cleanly.

One backend addition (the only non-.dark bit — worth review): NonBlockingConsole gains a capture buffer +
stdoutCaptureStart/Stop builtins, so the CLI can run a command (or a view's render) and collect its output
as a string instead of writing to the frame. That's what powers the : command bar, the AI ask surface, the
Apps & Views previews, and the tree-native render peek.


Honestly not done, with reasons

  • Matter view is v1 — the status header + change set + actions. Folding History / Conflicts / Sync in as
    switchable sections, and a real +/- diff pane (the diff engine already exists in scm/review, just needs
    borrowing), is the fast-follow. See notes/scm-matter-view-design.md.
  • AI is prompts, not chat sessionsa asks (agent ask, reply in-frame; needs a provider key or local
    Ollama) and the board is demo data; conversation history / multiple sessions is the real chat UX, still to
    come.
  • View peek is convention-based, not by-type — the tree-native peek keys off a render-named fn. The real
    version is a by-type signal on the value ("this is renderable"), so any view lights up in the tree without a
    naming convention.
  • A typed target vocabulary — rename is one SetName today, but a Location can only point at package
    content. notes/location-reference-design.md sketches LocationReference (content-by-hash / DB / alias /
    SqlDB / file) and the sync boundary — a name always syncs, its target may be Matter or a local handle. That
    informs the Matter view and the tree.
  • layout.darkresizeable.dark — considered and skipped: Layout is the accurate name for the region
    model; the rename would be narrower and risky. Flagging rather than doing it silently.

Devices/Runs show "unavailable/empty" honestly in this env (no tailnet, no traces). Rebase's success path isn't
exercised end-to-end (merge's is).

…ve->Conflicts, Mesh->Devices, my Matter->SCM)

1-9 only reach the first nine of 14 views; ` now opens a jumpable picker of all views (↑↓/j/k,
⏎ or a digit to jump, esc). Footer advertises it. Also fixes the naming confusion Stachu
flagged: the Tree IS Dark Matter, so Tree->Matter; the SCM overview I'd mislabeled 'Matter'
is now 'SCM'; Resolve->Conflicts, Mesh->Devices. (gotcha: it's Stdlib.String.padStart, not
padLeft — runtime-only failure.)
…he tree at once

Edit view: n/enter opens a full editor prefilled with 'module <you>.Drafts' + a declaration.
saveEditing now honors a leading 'module Owner.Mod' line — parses it off as the target and
applies the declaration there — so you write the whole thing and ^s applies it without any
navigation. Verified: Edit -> n -> ^s saves example into Stachu.Drafts.
…e stats)

Inspect as a top-level tab had no selection context and rendered nothing useful; it now
shows an at-a-glance overview of Dark Matter — the package-stats view (top-level modules,
fn/type/value counts, per-module table) captured into the pane. (It's still Matter's live
right-hand detail pane too.)
… / a acknowledge

Top gap from the command audit: Home and the tab badge scream 'N to resolve' but the view was
read-only. Now the selected divergence resolves in place — o keeps your local hash, t keeps
the incoming, a acknowledges — via Sync.Conflicts.keep/acknowledge (same as the classic
conflicts command). Footer advertises the keys; empty case is safe (verified no crash).
The audit noted Tree d only did dependencies while the classic deps command does both. depsText
now shows two sections — Dependencies (uses) via depsGetDependencies + Dependents (used by) via
depsGetDependents [(location, kind)], names resolved. Verified on parsePTExpr (3 uses / 0 used-by).
Cursor onto a `render` fn in the Matter tree and its output renders right there in the
Inspect pane (capture-eval of `Module.render ()`), instead of source. renderPreview skips
re-highlighting content that's already ANSI-colored, so the rendered frame shows clean.
Verified on Apps.Views.AiChats.render -> the live thread board in Inspect. This is the
tree-native version of the Apps&Views peek.
…c-prompt fallback

The workbench is the experience now. Escape (and q) exit the CLI straight to the shell
instead of backing out to Home or dropping to the classic prompt. Removed the
leave-for-classic-prompt confirm; the classic prompt stays reachable only via
DARK_CLASSIC=1 at launch. Old commands live on the `:` bar, not behind an exit.
Exit leaves the alt-screen cleanly (verified: rc=0, shell restored).
…keys

The ? keymap said 'esc back / q quit' (pre-'Escape just leaves') and listed old
Changes/History groups. Now: Global shows 'esc/q quit' + the backtick view picker,
and there are accurate Conflicts (o/t/a) and SCM (commit/discard/branch/switch/merge/
rebase) rows. Also dropped the long dead helpText val (superseded by helpGroups).
@StachuDotNet StachuDotNet changed the title Replace CLI UX with much broader TUI app/IDE Experiment: Replace CLI UX with much broader TUI app/IDE Jul 19, 2026
…, add REPL, reframe Devices/AI/Apps/Traces

Collapses the 14 tabs to 10 (Home Matter Inspect REPL SCM Devices AI Apps Traces Docs):
- SCM absorbs Changes/History/Conflicts as tab-switched sections (conflict resolution lives here)
- Inspect rebuilt as the item page (source + used-by/uses references), not fake stats
- REPL: persistent eval log + a (faked) live-values sidebar
- Devices reframed around instances + sync (no tailscale branding)
- AI/Apps/Traces get honest reframed shells; Apps reads the real registry
- Values folded into Matter as a lens (V); Edit demoted to the n/e modal it already was
- Broad UX: view-index constants (single source of truth), context line (the pwd),
  partial shared cursor across views, in-view filter (f), consistent keymap

Compiles clean via reload-packages. Interactive run in this clone is blocked by a
packages-source config quirk (run-cli reads main-branch Cli packages), tracked separately.
… isolation fix

With 2+ dark containers up, ./scripts/* picks by `docker ps --last`, not the current
clone, so it can silently run in another clone's container (edits look ignored, wrong
branch's behavior). Documents how to check, and the dark-multi-style fix (per-clone
build volume + remapped ports) for coexisting backend-divergent clones.
Expand the multi-clone section into the whole dance learned the hard way:
pinning work to a clone's container via explicit docker exec; the shared
dark_build + fixed-ports trap and the dark-multi-style isolation fix; and the
fresh-container bootstrap order (build via _build-server not bare compile;
fresh-from-schema DB not stale seed, whose Release stamp skips the origin_ts
migration; empty package-ref-hashes.txt before reload).
The Values lens (V in Matter) lists items by full path, but detailLines looked
them up as leaf-in-current-location, so the preview/reader showed '(not found)'.
Route detailLines through thingDetail (full-path lookup) when the lens is active.
Found by driving the running build.
startNew prefilled 'owner. + join(modPath)', but modPath (the current location)
already starts with the owner, so authoring from inside /Stachu offered
'Stachu.Stachu.'. Use modPath as-is when non-empty; fall back to the account
owner only at the root. Found by driving the running build.
- Devices: a real sectioned device/sync manager — this instance, example peers
  (online/offline dots), per-branch sync status — instead of a wall of prose
- Traces: demo trace set when none are recorded, so the surface isn't just empty;
  richer detail (replay / make-test / walk hints)
- Home: fixed stale nav hints (were 'Tree/Changes/Resolve' + 'press 7'); now points
  at the real views
- breadcrumb: non-list views (Home/REPL/SCM/Devices) show their name, not '— 0 items'
Verified rendering live.
AI was just the demo agents board; add a Prompts subsection — a preview library of
reusable prompts (review-diff, gen-tests, name-this, ...) with a detail pane — and a
Tab switcher between Agents and Prompts, mirroring SCM's sections. Threads aiSection
through itemsForView/preview like scmSection. Fills out the last thin tab.
- Inspect item page gains a metadata line: kind · real content hash · stability
  (hash is genuine content-addressing; deprecation marker is placeholder)
- Apps 'i' now actually installs/uninstalls the selected app (updates the installed
  set and the ●/○ marker live), instead of pointing at the : bar
Both verified live.
The surprise was that Enter did nothing in Apps while install hid behind 'i'. Make ⏎
the primary row action (install/uninstall), extract toggleInstall so ⏎ and i share it,
footer leads with '⏎ install'. Step toward: Enter = the row's main action, everywhere.
… lens, Devices opt-in

- ⏎ is now the row's primary action across views: Apps install, AI Prompts 'use'
  (fills the ask bar), AI Agents / Traces open the detail. (was: dead Enter, verbs
  hidden behind letters)
- values lens moves off V to L, so it stops clashing with v = new value
- Devices is opt-in: hidden from the tab bar / number keys / cycle / picker unless
  config's devices.enabled is true. Config-driven (cli-config.json via the existing
  config command / the : bar), not an env var. Tab bar + nav now operate on the
  visible-view set, so hiding Devices leaves no gap (key 6 → AI, not a hole).
Verified live: default 9 tabs, config flips to 10.
…ock data)

- context line shows the instance's name (config instance.name, else hostname) instead
  of 'local', and drops the unclear 'patch: Quick edits' segment
- each instance registers a UUID (instance.id) in cli-config.json on first run; name is
  config-overridable (config set instance.name major)
- Devices 'this instance' row uses the same name
- every faked surface now says '(mock data)' explicitly, not 'preview' (Devices peers +
  branch sync, AI agents + prompts, traces detail)
…st 1-9

Number keys don't scale with a variable tab count (and Devices being opt-in already
means key N != view N). Add ctrl+left/right to move between the visible tabs (verified
it decodes reliably through Console.ReadKey, unlike alt+arrow). Keeps [ ]/cycle, the
` picker, and 1-9 as alternates; help overlay now leads with ctrl+←/→.
Adds Branches as the 4th SCM section (tab-cycled): lists branches with the current one
marked, detail pane per branch. ⏎ switches, b new, r rename, x delete — real CRUD over
SCM.Branch.*. Verified live (create/switch/delete).
…s/Calendar

Refactors view dispatch so the core stops knowing about the extensible surface. Views are now a
composed list of `ViewDef` values (id, name, gate, loadItems, render, detail, hints) — the same
function-fields-in-a-record shape as `Cli.Registry.CommandHandler`. The core dispatchers
(visibleViews / itemsForView / previewLines / renderViewBody / hintsForView) delegate generically
to that registry for anything past the built-ins, so adding a view is one `extensionViews` entry
plus a few small fns, not edits scattered across five functions.

---

Why: adding Errors/Crons/Calendar (and the login-gated per-account views to come) was forcing the
main source to name each new view in visibleViews, the item loader, the preview, the body render,
the hints, the tab names, and the gating list. That's the opposite of composable. The registry
makes the extensible views additive and keeps account-name gating out of source entirely.

Shape:
- `type ViewDef` holds behavior as function-typed fields. Built-ins (indices 0-9) still live inline
  in the core because it owns their custom key handling; extension views (10+) are positions in
  `extensionViews ()`, never referenced by name.
- `viewSpecs ()` unifies base + extensions into (index, label, gateKey); visibleViews, the picker,
  the tab bar, and the launch-time gate all derive from it.
- Gating is per-ViewDef `gateKey` resolved by `gateEnabled` (config value = `true`/account-list),
  so per-user visibility is data the user sets, not `isStachu` in the code.
- `Frame.renderTabBar` now takes resolved (index, label) pairs, so the frame no longer owns a name
  table for extension views.

New views (all `(mock data)` labeled): Errors (real conflicts + mock 500s/test/deprecation/perf),
Crons (schedule/last/next rows + detail), Calendar (month grid + agenda, gated).

Gotcha worth noting: inline lambdas in the registry tuples left the field types as `_` and blocked
record coercion — every ViewDef field has to be a named function reference (which is also why
CommandHandler is written that way). Verified live: registry resolves at runtime, function-field
invocation works, and the tab bar composes/gates correctly.

Claude-Session: https://claude.ai/code/session_015BbLwctE41vpgJksNa9X4C
….enabled

The first login-tier view (#4.B): a Builtins tab listing all 704 F# builtins with their signature +
purity + description in the preview. Real data via `Builtin.getAllBuiltinFns ()`, reusing the
`builtins` command's type/purity formatting.

It's just another `ViewDef` in the registry, gated on `internal.enabled` — so
`config set internal.enabled stachu,feriel` turns the whole Darklang-internal tier on for those two
accounts and no one else. No account names in source; the tier is config data. This is the payoff of
the registry refactor: a login-gated dev-tools view is one entry, nothing in the core changes.

Verified live: the Builtins tab appears in the bar only when the internal gate names the current
account, and the list loads all 704 sorted.

Also updates the phase-plan notes: 3.A-3.C and 4.A done, 4.B done, 4.C in progress via the gate.

Claude-Session: https://claude.ai/code/session_015BbLwctE41vpgJksNa9X4C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant