@monster-orange/den 0.2.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1731 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +730 -6
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +12 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +173 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +192 -0
- package/src/shared/agents/runtimes.ts +47 -0
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +257 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/sessions/client.ts +277 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +79 -0
- package/src/shared/sessions/host.ts +667 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/agents-command.ts +812 -0
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +260 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +18 -5
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +580 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/sessions-command.ts +718 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +145 -20
- package/src/ui/mcp/server.ts +27 -2
- package/src/ui/mcp/tools.ts +94 -12
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/audit.ts +35 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +539 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +37 -3
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +48 -18
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,1737 @@ All notable changes to Den are documented here. Each release lists the
|
|
|
4
4
|
config and manifest changes a distribution must make to move to it under
|
|
5
5
|
**Migrations**.
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-11-06
|
|
8
|
+
|
|
9
|
+
0.4.0-dev.2 and later are built on 0.3.0 and include all of it (below).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`<cli> doctor --probe`** (0.4.0-dev.3, AG spec §5a-env; a consumer's
|
|
14
|
+
dogfood finding). Opt-in: it starts a short probe session through the
|
|
15
|
+
real session host for a plain session and for each available runtime's
|
|
16
|
+
agent env (built as `agents start` builds it, running a tiny node script
|
|
17
|
+
instead of the runtime's CLI). The probe prints key names and whether
|
|
18
|
+
each is credential-shaped, never values. It exits 1, one row per finding,
|
|
19
|
+
when a runtime's `scrubEnv` name or a credential-shaped variable reaches
|
|
20
|
+
either; without the PTY module it says unavailable, not failed. Plain
|
|
21
|
+
`doctor` still never spawns anything, and adds a note to run `<cli>
|
|
22
|
+
doctor --probe` when agents are configured (an `agents:` block or an
|
|
23
|
+
agent record).
|
|
24
|
+
- **`sessions start --keep-env NAME`** (0.4.0-dev.3): one exact name per
|
|
25
|
+
flag, repeatable, no globs. The session host's log names each kept
|
|
26
|
+
variable and who asked, never its value, and marks a credential-shaped one
|
|
27
|
+
(it is still passed). Refused for an agent (`DEN_AGENT` set); `agents
|
|
28
|
+
start` has no `--keep-env`.
|
|
29
|
+
- **Runtime contract: `envPassthrough` and `scrubEnv`** (0.4.0-dev.3). A
|
|
30
|
+
runtime names extra caller variables it needs (den-claude:
|
|
31
|
+
`CLAUDE_CONFIG_DIR`) and names it owns that must never reach a session
|
|
32
|
+
(exact, or a prefix ending in `*`; den-claude: `CLAUDECODE`,
|
|
33
|
+
`CLAUDE_CODE_*`). Every runtime's `scrubEnv` is removed from every
|
|
34
|
+
session's env last, whatever put it back. `LaunchOutput.warnings` are
|
|
35
|
+
printed at start.
|
|
36
|
+
- **den-claude passes `--permission-mode`** (0.4.0-dev.3; a consumer's
|
|
37
|
+
dogfood finding): `default` unless the persona's `permission_mode` sets
|
|
38
|
+
`default`, `acceptEdits`, `plan` or `bypassPermissions` (anything else is
|
|
39
|
+
an error naming the key), so `approvals: "terminal"` means prompts reach
|
|
40
|
+
the terminal. `bypassPermissions` prints a one-line warning at start.
|
|
41
|
+
- **`GET /api/activity?address=workspace:<ws>`** (0.4.0-dev.3; a consumer's
|
|
42
|
+
dogfood finding): an agent's cross-workspace call under a grant, which has
|
|
43
|
+
no repo or service, is listed under the workspace it reached and the
|
|
44
|
+
agent's own. Before, it showed only in the server log.
|
|
45
|
+
- **Agents: personas, runtimes and the agent record** (0.4.0-dev.1, AG-2a).
|
|
46
|
+
`<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>]
|
|
47
|
+
[--id <id>]` resolves the persona and the runtime, asks the runtime for
|
|
48
|
+
argv, env and files, and starts the agent as session `<dist>-<ws>:<id>`
|
|
49
|
+
in the workspace's checkout dir (what `/api/workspaces` reports as its
|
|
50
|
+
`resolvedDirs`), capped by `agents.max_concurrent`. `agents stop <id>`
|
|
51
|
+
ends its session (TERM) and marks it stopped; `agents resume <id>`, only
|
|
52
|
+
when its runtime declares resume and its session is not running, starts a
|
|
53
|
+
new session in the same cwd continuing the last run. `agents personas
|
|
54
|
+
[--json]` lists the personas. `agents list -w <ws>` now shows each
|
|
55
|
+
agent's persona, runtime, model, state and cwd. Attach with `sessions
|
|
56
|
+
attach`.
|
|
57
|
+
- **The agent record**: `<stateDir>/agents/<id>.json` (0600, directory
|
|
58
|
+
0700): id, persona, runtime, model, workspace, cwd, session, start and
|
|
59
|
+
stop times, and the runtime's resume handle. Never the env or the argv.
|
|
60
|
+
An id is unique in a distribution: the default is the persona's id, or
|
|
61
|
+
`<persona>-<ws>` when another workspace has that one.
|
|
62
|
+
- **Personas** are markdown files with YAML front matter; the body is the
|
|
63
|
+
role prompt. The format is a Claude Code agent file read as it is
|
|
64
|
+
(`name`, `description`, `model`, `tools`; `model: inherit` means none),
|
|
65
|
+
with Den's own keys beside it: `id`, `role`, `runtime`, `standing`,
|
|
66
|
+
`context` and `argv`. A pack is a directory of them; a pack with an
|
|
67
|
+
`agents/` directory (a Claude Code plugin) is read from there. Den ships
|
|
68
|
+
a default pack: planner and orchestrator (standing), fe-dev, be-dev,
|
|
69
|
+
devops, reviewer and ux-designer, with short runtime-neutral prompts.
|
|
70
|
+
- **Runtimes** are strategy kind `runtime` (`<cli> doctor` lists it).
|
|
71
|
+
`den-claude` (default) finds `claude` on PATH and reads its version from
|
|
72
|
+
the install without running it; it launches `claude --session-id <id>
|
|
73
|
+
--mcp-config <file> --strict-mcp-config [--model <m>]
|
|
74
|
+
--append-system-prompt <role prompt>` and resumes with `--resume <id>`.
|
|
75
|
+
`generic` runs the persona's `argv` template, substituting `{model}`
|
|
76
|
+
and `{mcp_config}`; it declares no resume.
|
|
77
|
+
- **An agent's MCP is pinned to its workspace**: its MCP config
|
|
78
|
+
(`<stateDir>/agent-files/<id>/mcp.json`, 0600) runs `<cli> mcp
|
|
79
|
+
--workspace <ws> --actor agent:<id>`, plus `--grant <ws>` for each
|
|
80
|
+
workspace in `agents.<id>.grants`.
|
|
81
|
+
- **`<cli> mcp --grant <ws>`** (0.4.0-dev.1, AG spec §5b; repeatable): a
|
|
82
|
+
scoped server also serves that workspace, only when the org config's
|
|
83
|
+
`agents.<id>.grants` lists it (a flag alone is refused, exit 2). A call
|
|
84
|
+
naming a granted workspace is reported before it runs, and the web
|
|
85
|
+
server's audit entry names both workspaces (`workspace` and
|
|
86
|
+
`targetWorkspace`). `workspaces` lists the scoped workspace and its
|
|
87
|
+
grants; tests run when the focused workspace is either. Logs and manifest
|
|
88
|
+
checks stay on the scoped workspace's own services.
|
|
89
|
+
|
|
90
|
+
- **Sessions: terminals that outlive the web server** (0.4.0-dev.0,
|
|
91
|
+
AG-3a). `<cli> sessions start --workspace <ws> --name <n> [--cwd <dir>]
|
|
92
|
+
-- <argv…>` runs argv in a PTY named `<dist>-<ws>:<n>`; `sessions list
|
|
93
|
+
[-w <ws>] [--json]` lists them (`-w` shows that workspace's only);
|
|
94
|
+
`sessions attach <name|id>` is a raw terminal on one (output fans out to
|
|
95
|
+
every attachment, scrollback replays first, SIGWINCH resizes, ctrl-]
|
|
96
|
+
detaches and leaves it running); `sessions kill <name|id> [--signal
|
|
97
|
+
INT|TERM|KILL]` signals its process group (TERM by default) or, once it
|
|
98
|
+
has exited, removes it and its scrollback. A name is the full name,
|
|
99
|
+
`<ws>:<n>`, a bare `<n>` only one session has, or the id. Usage errors
|
|
100
|
+
exit 2.
|
|
101
|
+
- A **session host** owns every PTY: `<cli> sessions host`, one detached
|
|
102
|
+
process per distribution (its own session and process group), started
|
|
103
|
+
on demand by the first client and serving `<stateDir>/sessions.sock`
|
|
104
|
+
(mode 0600). Restarting the web server, or the CLI that started a
|
|
105
|
+
session, leaves it running. A second host finds the first and stands
|
|
106
|
+
down; a socket nobody answers on is stale and is replaced. `sessions
|
|
107
|
+
host --stop` stops it (its running sessions end). The host logs to
|
|
108
|
+
`<stateDir>/logs/<cli>/sessions-host.log`. Its protocol is versioned
|
|
109
|
+
(newline JSON, v1).
|
|
110
|
+
- **Scrollback** is a 2 MB ring per session, flushed to
|
|
111
|
+
`<stateDir>/sessions/<id>.log` (0600), so after a host restart a session
|
|
112
|
+
is listed as exited and attaching replays its history. A record holds
|
|
113
|
+
the name, cwd, pid, times and exit code, never argv or env.
|
|
114
|
+
- **`agents.max_concurrent`** (default 4) caps the sessions running at
|
|
115
|
+
once; a start past it is refused, naming the key. A value that is not a
|
|
116
|
+
whole number of at least 1 is an error naming the key.
|
|
117
|
+
- **`<cli> agents list -w <ws> [--json]`** (0.4.0-dev.0): that workspace's
|
|
118
|
+
sessions as agents. Persona and runtime come from the agent record
|
|
119
|
+
(0.4.0-dev.1); a session started with `sessions start` has them blank.
|
|
120
|
+
- **`<cli> doctor` lists the session backend** (0.4.0-dev.0): `den-pty`,
|
|
121
|
+
available when the PTY module loads, else why not. It is never a doctor
|
|
122
|
+
failure.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **A session's environment is an allowlist** (0.4.0-dev.3, AG spec
|
|
127
|
+
§5a-env; a consumer's dogfood finding). Every session started an agent
|
|
128
|
+
with the caller's whole environment, so an agent started from inside
|
|
129
|
+
Claude Code inherited `CLAUDECODE` and `CLAUDE_CODE_*` (Claude Code then
|
|
130
|
+
turned transcripts off, so resume could not work, and the agent could
|
|
131
|
+
message peers as its parent), and every secret in the caller's env reached
|
|
132
|
+
every agent. Now every session's env starts empty: `PATH`, `HOME`, `USER`,
|
|
133
|
+
`LOGNAME`, `SHELL`, `TERM` (default `xterm-256color`), `COLORTERM`,
|
|
134
|
+
`LANG`, `LC_*`, `TZ` and `TMPDIR`; then, for a plain session, the names
|
|
135
|
+
given with `--keep-env`; for an agent, its runtime's `envPassthrough`, its
|
|
136
|
+
launch env and `DEN_AGENT=agent:<id>`; then every runtime's `scrubEnv` is
|
|
137
|
+
removed. `sessions start` names the variables it left out, never values.
|
|
138
|
+
An agent's MCP server gets the distribution's location (`DEN_ROOT`,
|
|
139
|
+
`<PREFIX>_ROOT`, `<PREFIX>_HOME`, when set) in its own config. The
|
|
140
|
+
credential patterns test workers are scrubbed with now live in Den
|
|
141
|
+
(`src/shared/secret-env.ts`), and the tests import them.
|
|
142
|
+
- **`agents resume` of a session that was never prompted starts fresh**
|
|
143
|
+
(0.4.0-dev.3; a consumer's dogfood finding): there is no conversation to
|
|
144
|
+
continue, so it starts under the same conversation id (`--session-id`, not
|
|
145
|
+
`--resume`) and says "starting fresh: it was never prompted, so there is
|
|
146
|
+
no conversation to resume". The session host records whether a session
|
|
147
|
+
ever got input with a line end (`prompted`), never the input.
|
|
148
|
+
- **`agents start` and `resume` report an early exit** (0.4.0-dev.3; a
|
|
149
|
+
consumer's dogfood finding): Den watches the new session for up to 3 s;
|
|
150
|
+
a non-zero exit prints the code and the last lines it printed (at most
|
|
151
|
+
10, escapes stripped, secret-shaped values redacted) and exits 1. Before,
|
|
152
|
+
it printed "resumed agent …" and exited 0 while the agent's CLI had
|
|
153
|
+
exited 1.
|
|
154
|
+
- **`agents list` shows agents only** (0.4.0-dev.3; a consumer's dogfood
|
|
155
|
+
finding): a session without an agent record (from `sessions start`) is
|
|
156
|
+
left out; `sessions list` shows it.
|
|
157
|
+
- **`sessions attach` prints its banner first** (0.4.0-dev.3; a consumer's
|
|
158
|
+
dogfood finding): `[attached to …]`, then the scrollback replay.
|
|
159
|
+
- **An MCP scope refusal names the agent's grants** (0.4.0-dev.3; a
|
|
160
|
+
consumer's dogfood finding): `(agent:<id>; grants: <a>, <b>)` or
|
|
161
|
+
`(agent:<id>; no grants)`.
|
|
162
|
+
- **A PTY module is an optional dependency** (0.4.0-dev.0, Q-AG-1):
|
|
163
|
+
`@lydell/node-pty` 1.1.0, exact. Its binaries are prebuilt per platform
|
|
164
|
+
(macOS and Linux, x64 and arm64; Windows) in packages npm installs with
|
|
165
|
+
it, with no install script and nothing downloaded. Installed with
|
|
166
|
+
`--omit=optional`, or on another platform, Den runs as before and
|
|
167
|
+
`sessions start` says den-pty is unavailable and why.
|
|
168
|
+
- **`agents list --json`** (0.4.0-dev.1): each row has `state` (running,
|
|
169
|
+
exited, stopped or ended) and, for an agent with a record, `persona`,
|
|
170
|
+
`runtime`, `model`, `workspace` and `cwd`; `session` is absent for a
|
|
171
|
+
record whose session is gone.
|
|
172
|
+
|
|
173
|
+
### Migrations
|
|
174
|
+
|
|
175
|
+
- **Plugins with a `den` range that ends below 0.4.0** (for example
|
|
176
|
+
`>=0.3.0-0 <0.4.0`) are skipped on 0.4.0, and `validate` fails them:
|
|
177
|
+
widen the range (for example `<1.0.0`). A plugin with no `den` range,
|
|
178
|
+
as Den's own themes, needs nothing.
|
|
179
|
+
- `agents:` is a new, optional block of the org config. Any key other
|
|
180
|
+
than `max_concurrent` and `persona_packs` is an agent id; an invalid value
|
|
181
|
+
is an error naming the key:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
agents:
|
|
185
|
+
max_concurrent: 4
|
|
186
|
+
persona_packs: [personas] # relative to the distribution root
|
|
187
|
+
planner:
|
|
188
|
+
grants: [other-workspace] # widens agent planner's MCP scope
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## [0.3.0] - 2026-11-06
|
|
192
|
+
|
|
193
|
+
0.3.0-dev.31 and later include all of 0.2.7 (below).
|
|
194
|
+
|
|
195
|
+
### Added
|
|
196
|
+
|
|
197
|
+
- **R26a's 422 page: "Config is invalid"** (0.3.0-dev.40). A page
|
|
198
|
+
navigation with a config that fails validation gets the shell frame
|
|
199
|
+
with a table of each problem (file, key path, message), the command
|
|
200
|
+
that shows them in a terminal (`<cli> validate`), "Reload once fixed",
|
|
201
|
+
and links to Logs and Settings, in en, ja and es. The rail stays
|
|
202
|
+
usable: `/settings` and `/logs` load whatever the config holds, and so
|
|
203
|
+
do the routes the shell and those pages need (`/api/settings`,
|
|
204
|
+
`/api/themes`, `/api/scope`, `/api/workspaces/settings`,
|
|
205
|
+
`/api/sudo-password/status`, `/api/environments` (dev.41), besides `/api/server-info`,
|
|
206
|
+
`/api/server-log`, `/api/changelog` and `/api/web/restart`). Any other
|
|
207
|
+
page answers 422 with this page.
|
|
208
|
+
- Org config validation lists every problem, not the first: a
|
|
209
|
+
ConfigError carries `issues` (`{ file, key, message }`), its message is
|
|
210
|
+
one line per issue as each always read, and the 422 JSON body gains
|
|
211
|
+
`issues`.
|
|
212
|
+
- **`up --dry-run` says what up will do about a pre-dev.37 record or a
|
|
213
|
+
held port** (0.3.0-dev.40, a consumer's dev.39 nit; it said only "Would
|
|
214
|
+
up"): "would adopt pid N (port P), nothing started", "would drop the
|
|
215
|
+
record (nothing running), then start it", or "port P held outside the
|
|
216
|
+
repo: would not start" (exit 1, as the real up). It signals nothing and
|
|
217
|
+
writes no record.
|
|
218
|
+
- Everything in 0.2.8 (scoped MCP for agents, orgs that own their sources,
|
|
219
|
+
config validation that stops every command) is in 0.3.0.
|
|
220
|
+
- **The UI in Japanese and Spanish** (0.3.0-dev.39, R19; R10's and R21's
|
|
221
|
+
Language).
|
|
222
|
+
- Catalogs for en, ja and es. en is shell.js's `MESSAGES.en`, the
|
|
223
|
+
fallback; ja and es are `public/i18n/<lang>.js`, which the server
|
|
224
|
+
loads before shell.js for a page in that language (with
|
|
225
|
+
`<html lang>`), so the first paint is already in it. Every lookup is
|
|
226
|
+
`t(key, vars)`; a `key.one` (any CLDR category) is the plural form
|
|
227
|
+
`Intl.PluralRules` picks for `n`, and numbers go through
|
|
228
|
+
`Intl.NumberFormat`.
|
|
229
|
+
- The language is a user pref, `language:` in `config/local/user.yml`
|
|
230
|
+
(absent: English), read and written by `/api/settings` (`ui_languages`
|
|
231
|
+
lists the choices; anything else is a 400). The user menu has a
|
|
232
|
+
Language section, each language named in itself, Settings → Appearance
|
|
233
|
+
a Language row, and ⌘K a "Language: …" command; a pick reloads the
|
|
234
|
+
page in it.
|
|
235
|
+
- The whole shell is converted: rail, top bar and scope menus, status
|
|
236
|
+
chip and drawer, ⌘K palette (its Settings rows too), Runs chip and
|
|
237
|
+
dock, lock popover, user menu, What's new, the stale-server pill and
|
|
238
|
+
restart overlay, the engine and network banners, the error toasts and
|
|
239
|
+
the 404 and 500 pages. The pages' own content (Workspaces, Graph,
|
|
240
|
+
Services, Logs, Tests, Environments, Releases, Edge and the Settings
|
|
241
|
+
page's rows) is still English: they move to `t()` page by page.
|
|
242
|
+
- Never translated: names (services, workspaces, repos, themes,
|
|
243
|
+
languages), logs, command output, run labels, and "Copy details"
|
|
244
|
+
(it is for a report, so it stays English).
|
|
245
|
+
- Guards: `i18n.test.ts` fails when any catalog lacks a key en has, has
|
|
246
|
+
one en doesn't, drops a placeholder, or uses a plural form its
|
|
247
|
+
language doesn't; when the code looks up a key en lacks; and when a
|
|
248
|
+
node-side `EN_FALLBACK` drifts from en. `i18n-shell.browser.test.ts`
|
|
249
|
+
draws the shell in a pseudo-locale with every overlay open and fails
|
|
250
|
+
on any word that skipped `t()`.
|
|
251
|
+
- English shows exactly what it did, apart from the new Language
|
|
252
|
+
controls.
|
|
253
|
+
|
|
254
|
+
- **Errors people can see and act on** (0.3.0-dev.38, R26, Ward QA
|
|
255
|
+
2026-11-05: "we don't handle 500 or 404s from the den server with
|
|
256
|
+
anything remotely obvious ... just text").
|
|
257
|
+
- A page request that fails gets the shell frame (rail, top bar, status
|
|
258
|
+
chip) around an error page (`error.html`, `error-page.js`). 404 "Page
|
|
259
|
+
not found" shows the path, "Did you mean …" from the command palette's
|
|
260
|
+
own matcher (`rankCommands`), links to Workspaces and Search (⌘K) and
|
|
261
|
+
the list of pages; a path under `/plugins/<id>/` says which plugin it
|
|
262
|
+
belongs to and that it isn't installed. 500 "Something went wrong"
|
|
263
|
+
shows the method and route template (never query values), the time,
|
|
264
|
+
a request id, Retry and Copy details, and no stack: the server log
|
|
265
|
+
has it under the same request id (`✗ GET /route failed (req
|
|
266
|
+
<id>): …`). Assets and non-browser requests keep a plain 404.
|
|
267
|
+
- Every `/api/*` error is JSON, one shape:
|
|
268
|
+
`{ ok: false, error, code, requestId }` (`code`: `not_found`,
|
|
269
|
+
`bad_request`, `internal`, `method_not_allowed`, `engine_stale`, …). It
|
|
270
|
+
holds at the response for every route, whatever the route sends, and a
|
|
271
|
+
route's own fields (`runCmd`, …) are kept. Every response carries
|
|
272
|
+
`X-Request-Id`; a failed request's log line carries the id.
|
|
273
|
+
- `denShell.api(method, url, body?)`: the one client path for a call. It
|
|
274
|
+
resolves with the data or throws a typed error (`DenApiError`: status,
|
|
275
|
+
code, requestId, network); one no page catches becomes a toast with
|
|
276
|
+
the message, the request id, Retry and Copy details
|
|
277
|
+
(`denShell.errorToast`). The shell's own calls, the scope and focus
|
|
278
|
+
menus, Services' workspace panel, the Workspaces page's add, create,
|
|
279
|
+
delete, rename, auto-start, mode and open-in-editor/terminal actions,
|
|
280
|
+
Releases' config save and deploy start, and the log search use it.
|
|
281
|
+
- When fetches fail at the network level (the server stopped or is
|
|
282
|
+
restarting), a banner says "Den's server isn't responding — retrying",
|
|
283
|
+
retries with backoff (1 s doubling to 15 s, or Retry now), and clears
|
|
284
|
+
itself when the server answers; meanwhile the status chip reads
|
|
285
|
+
"Status unknown", never a stale green.
|
|
286
|
+
- **A server older than the engine on disk says so** (0.3.0-dev.38, MD-2).
|
|
287
|
+
After a pin upgrades Den under a running web server, the server keeps the
|
|
288
|
+
old engine in memory while serving the new pages, so their new routes
|
|
289
|
+
404. The server records the engine's version and build id (a hash of its
|
|
290
|
+
package.json) at startup and compares them with the engine on disk on
|
|
291
|
+
page requests (cached for 3 s). On a mismatch every page leads with a
|
|
292
|
+
banner, "Den was updated to <new>. This server is still running <old>.
|
|
293
|
+
Restart it: `<cli> web restart` (or your service restart).", an unknown
|
|
294
|
+
route answers that message (`/api`: code `engine_stale` with `loaded`
|
|
295
|
+
and `onDisk`) instead of a 404, and a failed MCP tool call names both
|
|
296
|
+
versions and the restart. `/api/server-info` gains `loaded` and `onDisk`
|
|
297
|
+
(`{ version, buildId }`) and `engineStale`; its `version` is now the
|
|
298
|
+
engine the server runs.
|
|
299
|
+
- **DOC-1: the README's screenshots, generated** (0.3.0-dev.40; `npm run docs:shots`,
|
|
300
|
+
`scripts/doc-shots.mjs`). Runs Den on a scratch copy of
|
|
301
|
+
`examples/distribution` with the `examples/demo` repos as its workspace (a
|
|
302
|
+
docker shim, a random localhost port, a throwaway HOME, and a server env
|
|
303
|
+
built from HOME, PATH and the DEN_* it needs, never the caller's), and
|
|
304
|
+
writes to `docs/images/<version>/` (package.json's version without the
|
|
305
|
+
prerelease suffix): `hero-dark.png` and `hero-light.png` (the Graph page,
|
|
306
|
+
2x), `social-preview.png`, `workspaces.png`, `graph.png`,
|
|
307
|
+
`status-drawer.png`, `edge.png`, `runs-dock.png`, the `make up`
|
|
308
|
+
animation (`up.gif` with ffmpeg, else `up.webp` with img2webp, else
|
|
309
|
+
skipped with a message) and `alt.json`, the alt text. Size budgets: hero
|
|
310
|
+
400 KB a theme, the rest 200 KB, the animation 5 MB; over budget, the run
|
|
311
|
+
fails. Nothing reaches the tree unless every shot passes.
|
|
312
|
+
- The capture guard (`scripts/doc-shots-guard.js`): before each shot, the
|
|
313
|
+
page's innerText, every title, alt, aria-label and placeholder, and SVG
|
|
314
|
+
text go through the org-name gate's own matcher, now shared
|
|
315
|
+
(`scripts/org-names.js`, used by `org-name-gate.mjs` too). A hit aborts
|
|
316
|
+
the run naming the shot and the term, never the text around it, and
|
|
317
|
+
writes no image; so does a path under the real HOME. Without
|
|
318
|
+
DEN_ORG_NAMES or DEN_ORG_NAMES_FILE the script refuses (exit 2);
|
|
319
|
+
`--unguarded` is for local layout work only.
|
|
320
|
+
- The images stay out of the npm package: `files` has `!docs/images/`,
|
|
321
|
+
and dist-smoke fails on any image in the tarball.
|
|
322
|
+
- **`npm run docs:links`** (`scripts/docs-links.js`) checks that every
|
|
323
|
+
relative link in README.md and docs/ reaches a file and, with a
|
|
324
|
+
`#fragment`, a heading.
|
|
325
|
+
|
|
326
|
+
- **`stack_namespace`: a workspace can choose its stacks' Compose
|
|
327
|
+
namespace** (0.3.0-dev.36, Ward 2026-11-05). By default nothing changes:
|
|
328
|
+
a stack runs as Compose project `<stack>-<workspace>` (`default`:
|
|
329
|
+
`<stack>-main`), and so do its containers, volumes and networks. A
|
|
330
|
+
workspace can opt out with `stack_namespace: <slug>`, which replaces that
|
|
331
|
+
suffix: several workspaces can then share one namespace, and a stack
|
|
332
|
+
keeps its data when its repo moves to another workspace.
|
|
333
|
+
- Where: `workspaces.<ws>.stack_namespace` in
|
|
334
|
+
`config/local/workspaces.yml`, or `stack_namespace` on a blueprint
|
|
335
|
+
workspace (`defaults.workspaces.<n>`, next to `org`). First match wins:
|
|
336
|
+
workspaces.yml, the blueprint, then the workspace's own name.
|
|
337
|
+
`workspace seed` carries the blueprint's value into the workspace it
|
|
338
|
+
seeds. workspaces.yml overriding a different blueprint value is a
|
|
339
|
+
warning naming both, as for `org`. The value is lowercase letters,
|
|
340
|
+
digits and dashes; anything else is a config error naming the key.
|
|
341
|
+
- One resolver: every stack path (`stack` commands, `/api/stacks`,
|
|
342
|
+
Den's in-process compose health checks, a stack service's container
|
|
343
|
+
lookup, `workspace deactivate`'s running-stacks note) turns a workspace
|
|
344
|
+
into its suffix through `resolveStackWorkspaces`.
|
|
345
|
+
- A shared namespace means a shared instance, keyed by checkout. Two
|
|
346
|
+
workspaces bringing up the same stack from the same checkout (repo
|
|
347
|
+
path and branch) share one project: `stack down` (and `down <repo>`)
|
|
348
|
+
from one while another active workspace in the namespace still holds
|
|
349
|
+
it detaches instead of stopping it; the last one stops it
|
|
350
|
+
(`stack down --force` stops it regardless). A workspace skipping the
|
|
351
|
+
stack as already healthy is recorded as an owner too. The owners are
|
|
352
|
+
kept in `owners.json` in the project's edge state directory, and
|
|
353
|
+
re-derived on every decision (active, same namespace, still holds the
|
|
354
|
+
stack), like Den's own service refcount. Bringing it up from a
|
|
355
|
+
different checkout or branch is refused (`ServiceConflictError`,
|
|
356
|
+
`identity_mismatch`), naming both checkouts and the namespace: Den
|
|
357
|
+
never swaps the containers under another workspace.
|
|
358
|
+
- Visible: `workspace list` gains a Stacks column when a workspace's
|
|
359
|
+
namespace is not its default; the Workspaces page header shows a
|
|
360
|
+
"stacks: <ns>" chip; `GET /api/workspaces` adds `stackNamespaces`, each
|
|
361
|
+
workspace's resolved `stack_namespace`, its `source` (`workspace`,
|
|
362
|
+
`blueprint` or `default`), its `default` and whether it `differs`.
|
|
363
|
+
- `DEN_STACK_WORKSPACE` still wins over all of it, for that one process:
|
|
364
|
+
a per-command override (it also bypasses the owner refcount), not a
|
|
365
|
+
migration tool. To move a workspace's stacks onto another namespace
|
|
366
|
+
for good, set `stack_namespace`.
|
|
367
|
+
- **The graph's edges flow when a card is selected, and stay on top**
|
|
368
|
+
(0.3.0-dev.35, R22b, Ward's QA of dev.34). Selecting a card only
|
|
369
|
+
thickened its edges, and the edge layer painted under the group frames,
|
|
370
|
+
repo frames and cards, so an opaque frame hid an edge or an arrowhead
|
|
371
|
+
that crossed it (dev.34's routed cross-group edges too). The edge layer
|
|
372
|
+
now paints above the frames and cards, still taking no pointer events
|
|
373
|
+
(cards under an edge stay clickable). A highlighted edge carries a
|
|
374
|
+
marching-dash overlay, in its kind's colour, moving from the dependent
|
|
375
|
+
to its dependency (data-f to data-t) at about one cycle a second: a
|
|
376
|
+
selected card's dependencies flow out of it and its dependents flow in.
|
|
377
|
+
Under reduced motion nothing moves: a highlighted edge is drawn thicker
|
|
378
|
+
in the accent colour.
|
|
379
|
+
- **Workspaces: Local Dev is an ops page** (0.3.0-dev.35, R24a–e, Ward
|
|
380
|
+
2026-11-05: "the UX is a bit of a kitchen sink"). One job: run your
|
|
381
|
+
workspaces.
|
|
382
|
+
- R24a: the rail item, the page title and its header read
|
|
383
|
+
**Workspaces** (same icon and place). `/` and `/local-dev` still serve
|
|
384
|
+
it; the palette finds it as "Local Dev" too. The nav label key is
|
|
385
|
+
`n.workspaces`.
|
|
386
|
+
- R24b: the "⚙ Settings" footer under the workspace list is gone; "+ new
|
|
387
|
+
workspace" stays.
|
|
388
|
+
- R24c: the selected workspace's header adds **Open in Graph →**
|
|
389
|
+
(`/graph?ws=<name>`) and a ⋯ menu: Workspace settings
|
|
390
|
+
(`/settings?ws=<name>#workspace`, the old "Settings ↗" tab), Rename,
|
|
391
|
+
Delete.
|
|
392
|
+
- R24d: **one repo table** replaces the Graph, Applications and Config
|
|
393
|
+
tabs. Per repo: the health dot and rollup ("2/2 healthy", or the
|
|
394
|
+
failing check), the branch, the mode with "use local / use managed",
|
|
395
|
+
the last run's result and time (from the runs dock), Start, Stop,
|
|
396
|
+
Restart, Logs (in the dock) and ⋯ (terminal, editor, auto-start,
|
|
397
|
+
clone, validate, remove). A repo with several services expands to a
|
|
398
|
+
sub-row each (keyboard: the ▸ button, `aria-expanded`). Problems sort
|
|
399
|
+
first, then the startup order, with the fix inline: the status
|
|
400
|
+
drawer's text and Start/Logs buttons, from one source
|
|
401
|
+
(`topbar.js problemFix`). **Group by** None, Application (as `den
|
|
402
|
+
apps`, each application's Up/Restart/Down in its row) or Org, a user
|
|
403
|
+
pref (`graph.groupBy`). **Find** uses the Graph page's matcher. "+ Add
|
|
404
|
+
repo" sits under the table; "Validate a manifest" folds under it. The
|
|
405
|
+
empty state is unchanged. A real `<table>` with `th scope`; every
|
|
406
|
+
action button is named with its repo or service.
|
|
407
|
+
- R24e: on Workspaces the runs dock shows the selected workspace's runs
|
|
408
|
+
(a run belongs to every workspace that has its repo; one with no repo,
|
|
409
|
+
such as Sync, always shows), with an **All runs** toggle in the bar.
|
|
410
|
+
`denShell.runs.scope({ ws })`, and `runs.add(…, { ws })` tags a run.
|
|
411
|
+
- Not changed: the Graph page, Services, Settings and every API; no
|
|
412
|
+
consumer config needs a change.
|
|
413
|
+
|
|
414
|
+
- **The Graph page** (0.3.0-dev.32, R22, Ward 2026-11-05). Develop ›
|
|
415
|
+
Graph, after Local Dev, at `/graph`: the workspace graph alone, full
|
|
416
|
+
width under its page header, with the same toolbar, zoom, Maximize
|
|
417
|
+
(which now hides the page header too) and details panel as Local Dev's
|
|
418
|
+
Graph tab. It shows the workspace Local Dev would (`?ws=`, else the
|
|
419
|
+
view scope when it is one workspace, else the focus). Local Dev keeps
|
|
420
|
+
the workspace list, the workspace's actions and its tabs, Graph tab
|
|
421
|
+
included. Overview and Security stay out for now.
|
|
422
|
+
- **A page header on every page** (0.3.0-dev.32, R20). The shell draws the
|
|
423
|
+
prototype's `.ph` on each page that has no header of its own: the page's
|
|
424
|
+
title (`600 22px/1.2`, its one h1, which used to be visually hidden) and
|
|
425
|
+
a 13px muted line with the scope, `org / workspace / env`, kept in step
|
|
426
|
+
with the top bar (Local Dev and Graph name the workspace they show). Pages
|
|
427
|
+
keep their content below it. `denShell.pageHeader()` gives a page the
|
|
428
|
+
header's `act` (the primary action, at the end) and `tabs` (`.ptabs`,
|
|
429
|
+
below it), and `setScope` / `setSub` for the line. Local Dev's workspace
|
|
430
|
+
tabs are page tabs now: real tabs (`role="tab"`, `aria-selected`) at the
|
|
431
|
+
prototype's sizes (8px 12px, 14px, a 2px accent rule under the selected
|
|
432
|
+
one).
|
|
433
|
+
- **The stacks' routes on the Edge page and in `edge routes`**
|
|
434
|
+
(0.3.0-dev.32, from a consumer). The routers Den writes into
|
|
435
|
+
`edge/dynamic/stack-*.yml` when a stack comes up are neither Den's host
|
|
436
|
+
routes nor `edge include` files, so nothing listed them. They are listed
|
|
437
|
+
last, source "stack" with the file; `/api/edge` adds `stacks` and
|
|
438
|
+
`stackErrors` (a file that doesn't parse is named, never fatal).
|
|
439
|
+
|
|
440
|
+
- **The runs dock** (0.3.0-dev.30, R16). One output dock for every page, at
|
|
441
|
+
the foot of the content column, ported from the shell prototype: a bar
|
|
442
|
+
with "Runs", the run tabs (12px mono, a dot that pulses while running,
|
|
443
|
+
red when the run failed) and Show/Hide runs; open, a 170px output. The
|
|
444
|
+
Runs chip (R7) shows and hides it and counts the running tabs; an empty
|
|
445
|
+
dock links to the page that ran the last run. The bar carries Copy and
|
|
446
|
+
Clear for the selected run, a tab closes with its × (or Delete), ←/→ move
|
|
447
|
+
between tabs, and the dock's top edge drags to resize it. Pages and
|
|
448
|
+
plugins use one path, `denShell.runs` (`start`, `append`, `finish`, `set`,
|
|
449
|
+
`select`, `clear`, `remove`, `show`/`hide`/`toggle`, `list`, `onChange`);
|
|
450
|
+
a run started there gets "✓ done in {s}s" when it finishes. Since R16a
|
|
451
|
+
(below) the dock starts collapsed and open/closed is a user pref.
|
|
452
|
+
- **The Edge page** (0.3.0-dev.30, PL-SPEC §10). A built-in page in the
|
|
453
|
+
rail's Ship group, shown once `/api/edge` reports the edge configured
|
|
454
|
+
(`/api/edge?probe=1`, which asks no Docker). Read-only: the container and
|
|
455
|
+
its ports, the TLS strategy (`edge.tls`: none, mkcert or files) with
|
|
456
|
+
where it came from (set, mapped from the legacy keys, or implicit), CA
|
|
457
|
+
trust and the CA's directory, the cert and its names, the domains, and
|
|
458
|
+
every route: Den's own, then the routers in the included dynamic files
|
|
459
|
+
(`edge include`), each marked by its source file.
|
|
460
|
+
- **Room for Local Dev's graph** (0.3.0-dev.30, R23, Ward QA 2026-11-05).
|
|
461
|
+
The workspace list, the output and two toolbar rows left the graph at 40%
|
|
462
|
+
Fit. The workspace list now collapses to a 40px strip (« in its header;
|
|
463
|
+
» brings it back), remembered per user (`graph.workspaces`: `open` or
|
|
464
|
+
`collapsed`, on the Settings page under Graph). A Maximize button in the
|
|
465
|
+
graph's zoom group, or `F` with focus outside a field, hides the list,
|
|
466
|
+
the workspace header, the action row and the tabs; Esc restores. Fit
|
|
467
|
+
measures the whole area, so its zoom rises once maximized.
|
|
468
|
+
- **Included routers in `edge routes` and `/api/edge`** (0.3.0-dev.30,
|
|
469
|
+
PL-SPEC §10, from a consumer review). Routers defined in the distribution's
|
|
470
|
+
included dynamic files are served by Traefik but were never listed. Den
|
|
471
|
+
now reads their `http.routers` (read-only) and lists them after its own
|
|
472
|
+
routes with their source file; `/api/edge` adds `included`,
|
|
473
|
+
`includedErrors` (a file that doesn't parse is named, never fatal) and
|
|
474
|
+
`tls.from` / `tls.ca`. The rendered edge config is unchanged.
|
|
475
|
+
|
|
476
|
+
- **`<cli> relocate <repo> <path>`** (0.3.0-dev.26, from a consumer). A
|
|
477
|
+
checkout moved on disk: every workspace's dev-managed entries for the repo
|
|
478
|
+
(bare and `<repo>/<svc>`) now point at the new path, keeping each entry's
|
|
479
|
+
other settings. Managed entries (Den's own clones) are left alone, and focus
|
|
480
|
+
and the active set never change. It refuses a missing path, one whose
|
|
481
|
+
manifest name or GitHub remote names a different repo, and a repo no
|
|
482
|
+
workspace has. Since 0.3.0-dev.27 (from a consumer's dev.26 run) it also
|
|
483
|
+
refuses a folder where nothing names the repo (an empty or unrelated
|
|
484
|
+
folder): the manifest name, or the GitHub remote against the catalog's
|
|
485
|
+
`gh` (or the repo name when the catalog has none), must match.
|
|
486
|
+
`--force` accepts such a folder. The refusal's `--force` hint keeps
|
|
487
|
+
`--dry-run` and `-w` when given (0.3.0-dev.28). `--dry-run` shows the changes without writing; `-w` limits it
|
|
488
|
+
to one workspace.
|
|
489
|
+
- **`<cli> register <path> -w <workspace>`** (0.3.0-dev.26). `-w` writes that
|
|
490
|
+
workspace, active or not, and leaves focus alone. It used to fail with
|
|
491
|
+
"-w/--workspace is not valid with register". The no-TTY usage line names `-w` too
|
|
492
|
+
(0.3.0-dev.27).
|
|
493
|
+
|
|
494
|
+
- **The "What's new" pop-up shows the newest release only** (0.3.0-dev.16).
|
|
495
|
+
A "Show all versions (N earlier)" button in its footer lists every
|
|
496
|
+
earlier version in the same pop-up, and "Show latest only" collapses it
|
|
497
|
+
again. It always opens collapsed, at the top.
|
|
498
|
+
- **Themes (`den.theme/v1`).** The contract is `schemas/den.theme.v1.json`:
|
|
499
|
+
color tokens for dark and light, type, shape, a mark and self-hosted fonts.
|
|
500
|
+
`<cli> theme add <repo>[/<subpath>]@<tag|commit>` validates a theme and
|
|
501
|
+
vendors it into the distribution (`plugins/<id>/`, `plugins.lock.json`, shared with future plugin kinds). `theme add` never replaces a plugin dir it did not vendor (a local, unlocked plugin) or another kind's lock entry; `theme list` fails on a leftover 0.3.0-dev.0 `themes.lock.json`, naming it; `theme add` refuses a `file://` source (0.3.0-dev.7, below). A theme's and a font's `license` follow one rule: an SPDX expression, never UNLICENSED/NONE. `theme list` reports a theme that fails this Den's rules as "no longer valid" with the reasons, apart from "changed since it was added"; validate explains the license and CSS-value rules instead of printing "must NOT be valid". Local themes (the distribution's own, in `plugins/<id>/` with no lock entry) are listed as `local` and validated; `theme validate` with no argument checks every installed theme; `theme new <id>` starts a local theme from the built-in one;
|
|
502
|
+
`theme list` checks themes against the lock; `theme validate` and `theme
|
|
503
|
+
remove`. Contrast is checked per pair in both modes. A branch ref needs
|
|
504
|
+
`--track`. Den bundles its own theme, `den` 2.0.0. The public set and the
|
|
505
|
+
authoring guide: https://github.com/Monster-Orange/den-themes. See "Themes"
|
|
506
|
+
in `docs/distributions.md`.
|
|
507
|
+
- **The web UI is themed (0.3.0-dev.5).** Every page draws its colors from
|
|
508
|
+
the active theme, in dark or light: the engineer's choice (user.yml
|
|
509
|
+
`appearance: { theme, mode }`, from the header's new Mode and Theme menus),
|
|
510
|
+
else the org config's `brand.theme`, else `den`; an unknown or invalid id falls
|
|
511
|
+
back with one server-log warning. The server serves `/theme.css`,
|
|
512
|
+
`/theme/<id>/<file>` (only the files the manifest names) and `/api/themes`.
|
|
513
|
+
Switching writes user.yml only (no edge, services or restart). No page,
|
|
514
|
+
stylesheet or UI script hard-codes a color any more (a test holds that).
|
|
515
|
+
The header's version pill shows the running build, prerelease suffix
|
|
516
|
+
included (`v0.3.0-dev.5`, not the changelog's `v0.3.0`).
|
|
517
|
+
- **Plugins (`den.plugin/v1`, PL-1a and PL-2a, 0.3.0-dev.7).** The contract
|
|
518
|
+
is `schemas/den.plugin.v1.json`: `id`, `name`, `version`, `license` (the
|
|
519
|
+
theme schema's SPDX rule, by `$ref`), `den` (a version range, required),
|
|
520
|
+
`maintainer`, `readme`, `provides` (one entry per extension point: `theme`
|
|
521
|
+
with its `manifest`, `page` with its `entry` and `nav`, and the other kinds
|
|
522
|
+
by name) and `capabilities` (a closed vocabulary: `ui.nav`, `api.read`,
|
|
523
|
+
`api.actions:`, `secrets:`, `net:`, `exec:`, `send:`). A directory holding
|
|
524
|
+
only a `den-theme.json` is an implicit theme plugin, so every theme keeps
|
|
525
|
+
working. Every `plugins/<id>/` is a plugin: **local** when it has no lock
|
|
526
|
+
entry (the distribution's own, any kind; being present is its approval),
|
|
527
|
+
**added** when `plugin add` vendored it. A `plugins/<id>/` with neither
|
|
528
|
+
manifest is an error in `plugin list` and `plugin validate`, never
|
|
529
|
+
skipped, as are the id `den` and a directory name that is not an id.
|
|
530
|
+
- `<cli> plugin list`: every plugin with its version, kinds and `local`
|
|
531
|
+
or its source and ref; added ones are checked against the lock's hash
|
|
532
|
+
(as `theme list` does).
|
|
533
|
+
- `<cli> plugin validate [dir]`: every local and added plugin, or one dir
|
|
534
|
+
(a plugin, a distribution, or a repo of `themes/<id>/` and
|
|
535
|
+
`plugins/<id>/`). Admission rules: a `maintainer` (1); a browser- or
|
|
536
|
+
server-tier plugin's README names its tier and says what it must never
|
|
537
|
+
do (2; themes are exempt); an SPDX license and licensed fonts (3). A
|
|
538
|
+
local plugin that declares `api.actions`, `secrets`, `exec`, `net` or
|
|
539
|
+
`send` and has files git has not committed gets a warning (never an
|
|
540
|
+
error): committing it is its review.
|
|
541
|
+
- `<cli> plugin add <repo>[/<subpath>]@<tag|commit> [--track] [--yes]`,
|
|
542
|
+
`plugin update <id> @<ref>` and `plugin remove <id>`. The lock entry's
|
|
543
|
+
`kinds` and `approved_capabilities` come from the manifest; a plugin that
|
|
544
|
+
asks for capabilities needs `--yes`, and `update` asks again only when the
|
|
545
|
+
set grows. `remove` and `update` are for added plugins: a local one is
|
|
546
|
+
edited or deleted in git. A den-plugin.json plugin is vendored whole
|
|
547
|
+
(every file but dot-files); a theme still vendors only its named files,
|
|
548
|
+
so existing lock hashes still match.
|
|
549
|
+
- `add` and `update` refuse a `file://` source: "for your own plugin, put
|
|
550
|
+
it in plugins/<id>/ as a local plugin; to try a published one before
|
|
551
|
+
tagging, push a branch and use --track". This replaces 0.3.0-dev.2's
|
|
552
|
+
warning.
|
|
553
|
+
- `plugins.allow` in `den.yml` lists the `<host>/<owner>` pairs (or bare
|
|
554
|
+
hosts) `add` may fetch from. Absent: any host. A malformed value is an
|
|
555
|
+
error.
|
|
556
|
+
- `theme add | list | validate | remove` are `plugin` filtered to kind
|
|
557
|
+
theme, with the same output and exit codes; `theme new` is unchanged.
|
|
558
|
+
The web UI also finds a theme a `den-plugin.json` provides.
|
|
559
|
+
See "Plugins" in `docs/distributions.md`.
|
|
560
|
+
- **PL-SPEC draft-4 rulings (0.3.0-dev.8).**
|
|
561
|
+
- Rule 1 covers themes: a bare `den-theme.json`'s `author` is its
|
|
562
|
+
maintainer, and a theme plugin without one fails `plugin validate`.
|
|
563
|
+
`theme new` sets the author to the brand's name.
|
|
564
|
+
- The `den` range is checked (semver, prereleases included).
|
|
565
|
+
`plugin validate` and `theme validate` fail a plugin whose range
|
|
566
|
+
excludes the running Den, naming the range and the version. At load,
|
|
567
|
+
the web UI skips such a theme, whether the range is in its
|
|
568
|
+
`den-theme.json` or in the `den-plugin.json` that provides it, with one
|
|
569
|
+
server-log notice, and draws with the next choice (yours, then
|
|
570
|
+
`brand.theme`, then `den`). It never crashes. Den now depends on
|
|
571
|
+
`semver` (pinned).
|
|
572
|
+
- `plugin list` and `theme list` warn, never fail, about a lock entry
|
|
573
|
+
with a `file://` source (from 0.3.0-dev.2), and say how to fix it.
|
|
574
|
+
- **`plugin validate <id>` and `theme validate <id>` mean `plugins/<id>/`**
|
|
575
|
+
(0.3.0-dev.8). An id that names a dir in `plugins/` or a lock entry is
|
|
576
|
+
never read as a cwd-relative path. A bare name that isn't an id (such as
|
|
577
|
+
`x`) means `plugins/x/` when no such path exists. Anything else is a path,
|
|
578
|
+
as before. `theme validate den` checks the built-in theme. `theme
|
|
579
|
+
validate <id>` of a plugin that isn't a theme gives one ✗.
|
|
580
|
+
- **The theme reaches the type, the mark and links.** Every page draws text
|
|
581
|
+
in `var(--sans)` / `var(--mono)` / `var(--display)` (no page names a font
|
|
582
|
+
any more; a test holds that, like the color rule), so a theme's fonts
|
|
583
|
+
apply; `/theme.css` ends a theme's stack with Den's when it has no generic
|
|
584
|
+
family. The header shows the active theme's mark (SVG, `svg_dark` in dark
|
|
585
|
+
mode, or text in its `shape`) and redraws it on a live switch; favicon.ico
|
|
586
|
+
stays the favicon. Links in body content are underlined (subtly, in a tint
|
|
587
|
+
of their own color), so a red accent never makes a link read as an error.
|
|
588
|
+
- **Back to the org default.** The Theme menu starts with "Org default
|
|
589
|
+
(<name>)", which stores `appearance.theme: null`, so you keep following
|
|
590
|
+
`brand.theme`. Picking a theme by name stores its id, even the theme that
|
|
591
|
+
is the org default today (0.3.0-dev.11; dev.8 stored null for it): that
|
|
592
|
+
is a pin, selected as itself and labelled "<name> (pinned)".
|
|
593
|
+
`/api/themes` gives `org_default`. The popover's controls are named
|
|
594
|
+
(Mode, Theme; the button is "Appearance" with `aria-expanded` and
|
|
595
|
+
`aria-controls`); Escape closes it and returns focus to the button.
|
|
596
|
+
|
|
597
|
+
- **What fails `validate` never loads (0.3.0-dev.9).** The theme runtime's
|
|
598
|
+
verdict is now `plugin validate`'s for the whole plugin. That covers a bare
|
|
599
|
+
`den-theme.json`'s `author`, a `den-plugin.json`'s `maintainer`, README,
|
|
600
|
+
capabilities and `den` range, and the theme contract. A theme that fails
|
|
601
|
+
is left out of the Theme menu (even when it is your stored choice: you then
|
|
602
|
+
follow the org default) and is skipped at load with one server-log notice.
|
|
603
|
+
The fallback is the same as for the range: yours, then `brand.theme`, then
|
|
604
|
+
`den`. `/api/themes` still lists it, with `ok: false` and the problem.
|
|
605
|
+
- **A prerelease Den explains a range without `-0` (0.3.0-dev.9).** When
|
|
606
|
+
the running Den is a prerelease and only that keeps the range from
|
|
607
|
+
matching, the refusal adds "prerelease builds need a -0 range, e.g.
|
|
608
|
+
>=0.3.0-0", built from the range's own lower bound.
|
|
609
|
+
- **The left rail: grouped, pinnable, collapsible** (0.3.0-dev.17, R11,
|
|
610
|
+
R11a, R12, R13, R14). Pages sit under Develop (Local Dev, Services, Logs),
|
|
611
|
+
Verify (Tests) and Ship (Environments, Releases), with Settings at the
|
|
612
|
+
foot. Groups and items come from one registry in `shell.js`;
|
|
613
|
+
a distribution adds its own with `denShell.registerGroup({id, label,
|
|
614
|
+
order, tag})` and `denShell.registerNav({group, id, label, href, icon,
|
|
615
|
+
order, badge})`, and its labels with `denShell.addMessages(lang, {...})`.
|
|
616
|
+
- **Pinned**: ☆ on a Services card or in the graph's details pins a repo
|
|
617
|
+
or service to the top of the rail with its health dot ("Pinned shop");
|
|
618
|
+
× or Delete on the pinned row unpins it.
|
|
619
|
+
- **Badges**: Services shows how many services are down, live from the
|
|
620
|
+
health row; Logs how many log streams are open.
|
|
621
|
+
- **Collapse** (« at the foot) narrows the rail to its icons, 216 to 56px;
|
|
622
|
+
the headings and labels stay for screen readers. Under 720px the rail is
|
|
623
|
+
a drawer behind ☰.
|
|
624
|
+
- Pins and collapse are stored in user.yml `shell` (`GET`/`POST
|
|
625
|
+
/api/settings` `shell: {pins, collapsed}`); setting them changes nothing
|
|
626
|
+
else (the theme-switch isolation test covers it).
|
|
627
|
+
- **The shell matches the prototype's sizes** (0.3.0-dev.17, R1, R2, R3,
|
|
628
|
+
R9, R10): one 48px top bar with the brand, the workspace pill and Env as
|
|
629
|
+
key + value buttons, the health row as a chip, "What's new" and the ◐ ▾
|
|
630
|
+
menu (now with "All settings"); the rail's type, spacing and current-page
|
|
631
|
+
bar. `scripts/shell-fidelity.mjs` measures the real pages against the
|
|
632
|
+
prototype at 1440 and 1024 (1px, 0.5px for type) and lists any allowed
|
|
633
|
+
difference with its reason.
|
|
634
|
+
|
|
635
|
+
- **The scope breadcrumb is three menus** (0.3.0-dev.18, R4): `/ ORG
|
|
636
|
+
<org> ▾ / WORKSPACE <ws> ▾ / ENV <env> ▾`, each the prototype's `.ib`
|
|
637
|
+
button with a `.pop` menu (✓ on the current entry). The menus are ARIA
|
|
638
|
+
menus: ↓ or Enter opens one, ↑/↓, Home and End move, Enter picks,
|
|
639
|
+
Escape (or Tab) closes it and gives focus back to its button, and a
|
|
640
|
+
click outside closes it. They replace the workspace pill and its menu,
|
|
641
|
+
the env select, the interim View select and Services' own workspace
|
|
642
|
+
select. Below 860px the keys, separators and the org hide; below 720px
|
|
643
|
+
the scope does (the narrow layout, R11b, comes later).
|
|
644
|
+
- **Org:** one org is a static label with no ▾, as before (ORG-SPEC
|
|
645
|
+
C4). `denShell.setOrgs([{ id, title, workspaces? }], current,
|
|
646
|
+
onSelect?)` turns two or more into an Org ▾ menu; a pick calls
|
|
647
|
+
`onSelect(id)` and fires `den:org` on window. Nothing feeds it a list
|
|
648
|
+
yet: ORG-1b plugs `/api/orgs` in.
|
|
649
|
+
- **Workspace:** the view scope. "All active (N)" (with two or more
|
|
650
|
+
active) and each active workspace, the focused one tagged `focus`.
|
|
651
|
+
Picking one stores the view (user.yml `view`, `POST /api/scope`) and
|
|
652
|
+
reloads the page; it never moves the focus. A separate "Set <ws> as
|
|
653
|
+
focus" item (when the workspace in view isn't the focus) does Local
|
|
654
|
+
Dev's focus action, and "Manage workspaces…" opens Local Dev.
|
|
655
|
+
- **Env:** the same envs, value and browser storage as the select it
|
|
656
|
+
replaces; `#env-sel` stays (hidden) as the value pages read and the
|
|
657
|
+
`change` event they listen for.
|
|
658
|
+
- **A view scope: all active workspaces, or one** (0.3.0-dev.18). The
|
|
659
|
+
health row covered every active workspace while Services, Tests, Logs
|
|
660
|
+
and `status` showed only the focused one, so the row above a page listed
|
|
661
|
+
repos the page didn't. One source now says which repos are in view
|
|
662
|
+
(`src/shared/environment-manager/scope.ts`), and the health row,
|
|
663
|
+
`/api/services` (every page's list) and `<cli> status` all read it.
|
|
664
|
+
- "All active" is the default: it is what the health row already showed,
|
|
665
|
+
and with one active workspace it is the same view. A scope that is no
|
|
666
|
+
longer active falls back to all, never to an empty page.
|
|
667
|
+
- **The view is not the focus.** The focus stays the default target for
|
|
668
|
+
commands; the view only filters what pages show. Switching it writes
|
|
669
|
+
user.yml `view` and nothing else: no edge route, container, daemon or
|
|
670
|
+
workspaces.yml change, and the server doesn't restart (the
|
|
671
|
+
theme-switch isolation test covers it). `GET`/`POST /api/scope`.
|
|
672
|
+
- **A shared service is listed once, with its owners**: a repo that two
|
|
673
|
+
workspaces in view define shows one card (`shop · ws-a, ws-b`) and one
|
|
674
|
+
health dot, its deps and auto-start from the first (focus first).
|
|
675
|
+
`/api/services` rows carry `inWorkspaces`, `/api/health` rows carry
|
|
676
|
+
`workspaces`.
|
|
677
|
+
- **`<cli> status`** shows the same view and says so ("Scope: all active
|
|
678
|
+
(a, b, c) · focus: a"). Use `--scope <workspace>` for one (`--all`
|
|
679
|
+
already means `--verbose` there); `-w <workspace>` still checks that
|
|
680
|
+
one workspace.
|
|
681
|
+
- Local Dev edits one workspace at a time: it opens on the view's
|
|
682
|
+
workspace when that is one, else the focus.
|
|
683
|
+
- **A ready path for distribution scripts** (0.3.0-dev.18). A script that
|
|
684
|
+
loads before shell.js pushes functions onto `window.denShellQueue` (an
|
|
685
|
+
array); each runs with `denShell` once the shell exists, and a later push
|
|
686
|
+
runs at once. `den-shell:ready` fires on window when the shell is up.
|
|
687
|
+
- **`den:pins`** (0.3.0-dev.18): any pin change fires it on window
|
|
688
|
+
(`detail.pins`), and every pin toggle on the page repaints from it.
|
|
689
|
+
- **`org.title_source`** in `/api/<cli>/config` (0.3.0-dev.18; added beside
|
|
690
|
+
the existing fields, ORG-SPEC C3): `org.title`, `orgs`, or `id`.
|
|
691
|
+
- **A Settings page** (0.3.0-dev.19, R21): `/settings`, in the shell,
|
|
692
|
+
with the prototype's page header, sections, rows, segmented buttons,
|
|
693
|
+
swatches and navigation list, applied to every setting Den has.
|
|
694
|
+
- **Categories** on the left (a nav; the current one `aria-current`),
|
|
695
|
+
their rows on the right: Appearance, Editor & terminal, Navigation,
|
|
696
|
+
Graph, Workspace, Command line, Distribution, then any a distribution
|
|
697
|
+
or plugin registers (tagged with its `from`).
|
|
698
|
+
- **Search** (`/` focuses it) filters rows by label, description and
|
|
699
|
+
keywords; a match stays under its category's heading, empty
|
|
700
|
+
categories hide, "No settings match" shows when nothing does, and the
|
|
701
|
+
count is announced. The URL keeps `#<category>` and `?q=`, so a
|
|
702
|
+
setting can be linked to; reload and back/forward work.
|
|
703
|
+
- **Every row shows its scope**: User (config/local/user.yml),
|
|
704
|
+
Workspace (that workspace in config/local/workspaces.yml) or
|
|
705
|
+
Distribution (read-only). The Workspace rows follow the Workspace you
|
|
706
|
+
view; with All active, the section asks which workspace.
|
|
707
|
+
- **Writes go only to per-user, gitignored state**, as before:
|
|
708
|
+
`POST /api/settings` (user.yml) and `POST /api/workspaces/settings`
|
|
709
|
+
(workspaces.yml), with the same keys. Nothing writes except an
|
|
710
|
+
explicit change: a control writes on its change (the per-workspace
|
|
711
|
+
editor on Save, as before) and says "✓ Saved". Loading, searching,
|
|
712
|
+
moving between categories and changing the hash or query write
|
|
713
|
+
nothing. The page never writes the distribution's config.
|
|
714
|
+
- **Distribution rows are read-only**: the product name, GitHub org, org
|
|
715
|
+
labels, web port, environments, edge domain and the org default theme,
|
|
716
|
+
each with "Set by the distribution in config/<file>.yml". Your own
|
|
717
|
+
override of the same thing (your theme) is a separate User row.
|
|
718
|
+
`GET /api/settings` gains `distribution` (read from the org config
|
|
719
|
+
only), `user_web_port`, `local_dev` and `cli_tabs` (read-only).
|
|
720
|
+
- **Today's settings moved in, unchanged in storage**: Default IDE,
|
|
721
|
+
Per-language IDE and Default terminal (Local Dev's ⚙ panel); mode and
|
|
722
|
+
theme (◐ ▾ stays as a shortcut, and both drive the same prefs and
|
|
723
|
+
repaint each other); the graph prefs; the per-workspace editor (Local
|
|
724
|
+
Dev's workspace Settings tab). `log_window_lines`, `local_dev`,
|
|
725
|
+
`cli_tabs` and your `web.port` show read-only with the command that
|
|
726
|
+
sets them.
|
|
727
|
+
- **Navigation** (new): hide or show each rail page and move it within
|
|
728
|
+
its group; reorder and unpin pins; collapse the rail. Stored in
|
|
729
|
+
user.yml `shell` as `hidden` and `order` (page ids), beside `pins` and
|
|
730
|
+
`collapsed`. A view preference only: no edge route, container, daemon,
|
|
731
|
+
pid or restart changes (the theme-switch isolation test covers it).
|
|
732
|
+
- **The old entry points land on the matching category**: Local Dev's
|
|
733
|
+
⚙ (`#editor`), its workspace Settings tab (`#workspace`, that
|
|
734
|
+
workspace), `/local-dev?settings=1` (redirected to `#editor`), the
|
|
735
|
+
rail's Settings item and "All settings" in ◐ ▾ (`#appearance`). The
|
|
736
|
+
old panel is gone. Arriving at a category (a link, the hash,
|
|
737
|
+
back/forward) marks it current and puts focus on its heading.
|
|
738
|
+
- **The SETTINGS registry** (0.3.0-dev.19, R21), beside NAV and GROUPS on
|
|
739
|
+
`window.denShell` (and through `window.denShellQueue`):
|
|
740
|
+
`registerSettingsCategory({ id, label, order, description?, from? })`
|
|
741
|
+
and `registerSetting({ id, category, label, description, keywords,
|
|
742
|
+
scope: user|workspace|distribution, control, read(ctx), write?(value,
|
|
743
|
+
ctx), keys? })`; `settings()`, `settingsCategories()`; `den:settings`
|
|
744
|
+
fires on any change. A distribution row with a `write` is refused.
|
|
745
|
+
Controls: `seg`, `radio`, `select`, `number`, `toggle`, `text`,
|
|
746
|
+
`readonly` and `custom` (`render(el, ctx, { value, save })`). Den's own
|
|
747
|
+
rows are in `public/settings-registry.js`; a test fails if a key
|
|
748
|
+
`POST /api/settings` accepts has no row.
|
|
749
|
+
- **`denShell.setNavPrefs({ hidden?, order? })`, `setPins(ids)`,
|
|
750
|
+
`setCollapsed(bool)` and `orderedNav()`** (0.3.0-dev.19): the rail's
|
|
751
|
+
Navigation prefs, saved to `/api/settings` `shell`.
|
|
752
|
+
- **The top bar's search, status, runs and lock** (0.3.0-dev.20, R5, R6,
|
|
753
|
+
R7, R8, R10, R15, R17, R18). It ships together with the Settings page
|
|
754
|
+
(dev.19, never released on its own): the palette finds every setting,
|
|
755
|
+
and the user menu's "All settings" opens it. The bar now reads ☰, brand, scope, search,
|
|
756
|
+
then the Status, Runs and lock chips, "What's new" and the ◐ ▾ menu, at
|
|
757
|
+
the prototype's sizes (`scripts/shell-fidelity.mjs` measures it). The
|
|
758
|
+
search field gives way first, so the scope's parts never run into each
|
|
759
|
+
other; below 960px the scope's keys hide (860px in the prototype, whose
|
|
760
|
+
chips are narrower), below 860px the chips keep only their dots and
|
|
761
|
+
icons, and below 720px the scope hides and the search field is an icon.
|
|
762
|
+
- **Search and the ⌘K palette.** "Search pages, services, actions…"
|
|
763
|
+
(⌘K or Ctrl+K anywhere, or a click) opens a palette: pages, apps,
|
|
764
|
+
services with their health dot, Restart and Logs for each service,
|
|
765
|
+
Appearance (System, Dark, Light) and every installed theme. It ranks a
|
|
766
|
+
label's prefix, then a word in it, then anywhere, then keywords and
|
|
767
|
+
the kind, then the letters in order; every word must match. ↑/↓ move,
|
|
768
|
+
Enter runs, Escape closes and gives focus back. It is a modal dialog
|
|
769
|
+
with a combobox and a listbox (`aria-activedescendant`).
|
|
770
|
+
- **`denShell.registerCommand({id, label, keywords, kind, run})`**
|
|
771
|
+
(also from `window.denShellQueue`) adds a palette row; `kind` is
|
|
772
|
+
`page`, `app`, `service`, `action` or `setting`. Every row of the
|
|
773
|
+
SETTINGS registry (Den's, a distribution's or a plugin's) is a
|
|
774
|
+
`setting` row on every page: "terminal" finds Default terminal, and
|
|
775
|
+
Enter opens `/settings?q=<label>#<category>`.
|
|
776
|
+
`unregisterCommand(id)` and `commands()` too. `denShell.openMenu(btn,
|
|
777
|
+
items)` opens a `.pop` menu any script can use.
|
|
778
|
+
- **The status chip** replaces the health dot row and its "+N" chip:
|
|
779
|
+
"All N healthy", or "ok/N healthy · k down" with a bad border, from the
|
|
780
|
+
same rollup as the rail's Services badge. It shows "Checking…" until
|
|
781
|
+
the first cycle is in and paints once: no partial red on a cold load.
|
|
782
|
+
A pending check is pending, never down.
|
|
783
|
+
- **The status drawer** (the chip opens it): a right-hand modal panel,
|
|
784
|
+
focus on ✕, Escape back to the chip. "Needs attention" has one card
|
|
785
|
+
per down service, with what was checked, the error and when, and
|
|
786
|
+
Start, Logs and Pin; "Everything" lists each application ok/N, each
|
|
787
|
+
service with its time in ms. What the row's tooltips said is in each
|
|
788
|
+
row's title.
|
|
789
|
+
- **The Runs chip** counts the runs this page started (service and
|
|
790
|
+
workspace commands, syncs, test runs, deploys; not log tails), with a
|
|
791
|
+
pulse while one runs. Until the output dock (dev.21) it shows the
|
|
792
|
+
page's own output pane (marked `data-den-output`), else links to the
|
|
793
|
+
page of the last run.
|
|
794
|
+
- **The lock chip** (🔒 Locked / 🔓 Unlocked) holds the admin-password
|
|
795
|
+
form that was Local Dev's sudo banner, with Save to 1Password, in a
|
|
796
|
+
popover: "Den asks once per session. Agents can't unlock." It is the
|
|
797
|
+
one unlock on every page; the banner is gone. Local Dev still asks
|
|
798
|
+
1Password for a stored password on load; other pages ask only when
|
|
799
|
+
the popover opens. Start and Restart from the drawer or the palette
|
|
800
|
+
follow the same lock rule, then run on Local Dev (which runs them on
|
|
801
|
+
arrival when you are on another page).
|
|
802
|
+
- **The user menu** (◐ ▾) is the prototype's `.pop`: Appearance, Theme
|
|
803
|
+
and "All settings" (`/settings#appearance`), and a hidden slot for
|
|
804
|
+
Language. Its ◐ ▾ glyphs are hidden from screen readers; its name is
|
|
805
|
+
"Appearance". Opening any of these overlays writes
|
|
806
|
+
nothing; switching appearance or theme from the menu or the palette
|
|
807
|
+
writes user.yml `appearance` only (the theme-switch isolation test
|
|
808
|
+
covers it).
|
|
809
|
+
- `header.js` exports `requireUnlock`, `isUnlocked`, `onLockChange`,
|
|
810
|
+
`setServiceRunner` and `takePendingAction` for pages.
|
|
811
|
+
- **One h1 per page**: the brand title in the bar is a span now, not an
|
|
812
|
+
h1, so a page's own heading is its one. A page with none (Services,
|
|
813
|
+
Logs, …) gets a visually hidden h1 named from its title.
|
|
814
|
+
- **Raster theme marks** (0.3.0-dev.22): a mark can be a PNG or a WebP
|
|
815
|
+
(`png` / `png_dark`, `webp` / `webp_dark`), for a mark that exists only
|
|
816
|
+
as a raster. One image kind per mark (svg, png or webp): two fail
|
|
817
|
+
validate, naming both. `theme validate` and `theme add` check each file's
|
|
818
|
+
first bytes against its extension, a 64 KB cap and at least 44×44 px
|
|
819
|
+
(twice the bar's 22px mark), reading the PNG or WebP header itself.
|
|
820
|
+
`/theme/<id>/<file>` serves them as `image/png` and `image/webp`, still
|
|
821
|
+
only the files the manifest names. The top bar draws one as an image at
|
|
822
|
+
22px, clipped to the mark's `shape`, the dark file in dark mode. SVG and
|
|
823
|
+
text marks are unchanged.
|
|
824
|
+
- **The browser tab shows the theme's icon** (0.3.0-dev.22): the theme's
|
|
825
|
+
new optional `favicon` (`.png`, `.svg` or `.ico`, magic-byte checked, at
|
|
826
|
+
most 64 KB), else its mark's file (the dark variant in dark mode), else
|
|
827
|
+
Den's own icon. The shell sets it on every page (no page links
|
|
828
|
+
`/favicon.ico` any more) and changes it with the theme and the mode,
|
|
829
|
+
System following the OS included. Its URL,
|
|
830
|
+
`/theme-favicon/<id>?mode=<mode>&v=<version>-<hash>`, changes with each,
|
|
831
|
+
so a switch is never answered from cache. The built-in `den` theme names
|
|
832
|
+
Den's icon as its `favicon`; `theme new` leaves it out, so a new theme's
|
|
833
|
+
tab shows its own mark.
|
|
834
|
+
- **Secrets backends are registered strategies, and `none` is one**
|
|
835
|
+
(0.3.0-dev.23). Each built-in (`none`, `env`, `doppler`, `1password`)
|
|
836
|
+
registers through one registry (`src/shared/strategies/registry.ts`, kind
|
|
837
|
+
`secrets`) as `{ id, kind, detect(), capabilities }`: a factory, a label,
|
|
838
|
+
its CLI, which ref parts it needs, and optional `setup` and env
|
|
839
|
+
injection. `detect()` is a PATH check that never prompts or prints; a
|
|
840
|
+
backend whose CLI is missing reports "not available: The Doppler CLI
|
|
841
|
+
(doppler) is not on PATH" instead of failing a spawn. `secrets.provider:
|
|
842
|
+
none` resolves nothing and never runs a CLI. `den create` now scaffolds
|
|
843
|
+
`secrets: { provider: none }`, with the choices in a comment, so a new
|
|
844
|
+
distribution depends on no store. See "Secrets" in
|
|
845
|
+
`docs/distributions.md`.
|
|
846
|
+
- **The edge's TLS is a registered strategy: `edge.tls`** (0.3.0-dev.25,
|
|
847
|
+
TLS-1a). `none`, `mkcert` and `files` register through the strategy
|
|
848
|
+
registry as kind `tls`, as the secrets backends do. `tls: none` serves
|
|
849
|
+
http only (no https entrypoint, no cert work, mkcert never run);
|
|
850
|
+
`{ strategy: mkcert }` issues the edge's wildcard cert from a local CA,
|
|
851
|
+
its own or `ca:` (an existing CA, never minted or uninstalled);
|
|
852
|
+
`{ strategy: files, include, mounts }` serves the certs your included
|
|
853
|
+
config holds (`ca:` optional, for health checks). An unknown strategy,
|
|
854
|
+
or a key a strategy does not take, is a validation error naming the
|
|
855
|
+
built-ins. mkcert's `detect()` is a PATH check: with it missing,
|
|
856
|
+
`validate`, `doctor`, `edge up` and `edge trust` say "mkcert is not
|
|
857
|
+
installed" instead of a spawn error. `edge trust` / `untrust` exist under
|
|
858
|
+
mkcert, and `edge trust` also under `files` with a `ca` (it installs that
|
|
859
|
+
existing CA, as `caroot` did); elsewhere they exit 1 ("edge trust needs
|
|
860
|
+
edge.tls.strategy: mkcert (or files with a ca)").
|
|
861
|
+
`den create` scaffolds `edge.tls: none`, with the mkcert and files forms
|
|
862
|
+
in a comment. `/api/edge` adds `tls: { strategy, implicit }`. See "The
|
|
863
|
+
edge and TLS" in `docs/distributions.md`.
|
|
864
|
+
- **`<cli> doctor`** (0.3.0-dev.25): each strategy kind (secrets, tls),
|
|
865
|
+
the strategy selected, whether it was implicit, and its `detect()` with
|
|
866
|
+
the reason. Read-only: a PATH check, never a backend CLI, a prompt or a
|
|
867
|
+
secret value. Exit 1 when a strategy that will be used cannot work.
|
|
868
|
+
|
|
869
|
+
### Changed
|
|
870
|
+
|
|
871
|
+
- **`down` works under an invalid config** (0.3.0-dev.40): it is
|
|
872
|
+
config-free, so stopping is never stranded. With a config that fails
|
|
873
|
+
validation it says so ("Stopping anyway, without the config's org
|
|
874
|
+
data") and stops from the daemon records and compose projects; `down -a`
|
|
875
|
+
too. Every other command still stops first.
|
|
876
|
+
- **Only up and restart detach by default** (0.3.0-dev.40, DOC-1). Every
|
|
877
|
+
string command but down and setup ran detached, so `preflight` run from
|
|
878
|
+
the Services page never finished. A command other than up and restart
|
|
879
|
+
now runs to completion unless it says `detachable: true`.
|
|
880
|
+
- **English plurals** (0.3.0-dev.40): en has the `.one` forms (`org.n.one`
|
|
881
|
+
"{n} workspace", and the status, badge, drawer and changelog counts), so
|
|
882
|
+
the org menu no longer says "1 workspaces". The catalog test fails when a
|
|
883
|
+
count another catalog makes singular has no en `.one`.
|
|
884
|
+
- **The graph's top-level groups wrap to the stage** (0.3.0-dev.34, a
|
|
885
|
+
consumer's dogfood finding: 31 services in 10 org groups). Top-down,
|
|
886
|
+
every org (or application) group sat in one row, so the whole graph
|
|
887
|
+
fitted only at 15–24%, where cards are hard to read. Each top-level
|
|
888
|
+
group is now laid out on its own by the same layered layout (a group's
|
|
889
|
+
inside is unchanged), and the groups wrap into rows (top-down) or
|
|
890
|
+
columns (left-right), choosing the wrap that fits the stage at the
|
|
891
|
+
highest zoom; the edges between groups are routed through the gaps
|
|
892
|
+
between rows. A 31-card, 10-org graph fits at 45% at 1440×900 (was
|
|
893
|
+
22%) and 33% at 1024×768 (was 14%).
|
|
894
|
+
- **The automatic fit shows the whole graph** (0.3.0-dev.34). A graph
|
|
895
|
+
that needed less than 25% used to open on its top at 60%; the first
|
|
896
|
+
paint now fits it as Fit does (down to 10%).
|
|
897
|
+
|
|
898
|
+
- **The Graph page follows the top bar's workspace scope** (0.3.0-dev.33,
|
|
899
|
+
a consumer's dogfood finding). With the top bar's Workspace on "All
|
|
900
|
+
active", `/graph` used to show (and name) the focused workspace only. It
|
|
901
|
+
now shows every active workspace in one graph, as the Services page
|
|
902
|
+
lists them: one card per service, a service several workspaces define
|
|
903
|
+
listed once with its owners (`demo-api · main, notes`). One workspace
|
|
904
|
+
picked shows that one, and `?ws=` still wins. The page header's line
|
|
905
|
+
names what is shown (`acme / All active / local`). A card's actions run
|
|
906
|
+
in the workspace that owns its repo (the first in view, focus first).
|
|
907
|
+
Local Dev keeps one workspace, the list's selection: its actions, its
|
|
908
|
+
Applications and Config tabs and its Graph tab are per workspace.
|
|
909
|
+
- **Stack routes on the Edge page and in `edge routes`: scheme and
|
|
910
|
+
upstream** (0.3.0-dev.33, a consumer's dogfood finding). A stack
|
|
911
|
+
router's URL is shown with its scheme like Den's own rows: `https://`
|
|
912
|
+
when the router has TLS or is on the `websecure` entrypoint, else
|
|
913
|
+
`http://`. UPSTREAM shows the service's `loadBalancer` server URL when
|
|
914
|
+
the router's file defines it, else the Traefik service's name. The
|
|
915
|
+
routers in `/api/edge` (`included`, `stacks`) carry `secure` and
|
|
916
|
+
`upstream`; included routers get the same.
|
|
917
|
+
- **Page tabs are WAI-ARIA tabs** (0.3.0-dev.33, a consumer's dogfood
|
|
918
|
+
finding: ArrowRight, Home and End left focus on Graph). Local Dev's
|
|
919
|
+
workspace tabs and any tabs `denShell.pageHeader().tabs(spec)` makes
|
|
920
|
+
share one helper, `denShell.wireTabs(tablist, opts)`: one tab stop (the
|
|
921
|
+
selected tab is `tabindex=0`, the rest `-1`), ←/→ move and wrap, Home and
|
|
922
|
+
End go to the ends, and a tab is selected as focus moves to it
|
|
923
|
+
(automatic activation). Each tab's `aria-controls` names its
|
|
924
|
+
`role="tabpanel"`, whose `aria-labelledby` names the tab, and the other
|
|
925
|
+
panels are hidden. Local Dev's "Settings ↗" link now sits beside the
|
|
926
|
+
tablist rather than in it. `pageHeader().tabs` is a function now: the
|
|
927
|
+
shell no longer adds a hidden, unlabelled, empty `.ptabs` tablist to
|
|
928
|
+
every page, only the one a page asks for.
|
|
929
|
+
|
|
930
|
+
- **POST /api/settings: null clears a pref** (0.3.0-dev.32, from a
|
|
931
|
+
consumer). A key set to `null` (`{"graph":{"workspaces":null}}`, or in
|
|
932
|
+
`shell` or `appearance`) is removed from config/local/user.yml instead of
|
|
933
|
+
failing with 400, and a block left empty goes too; a whole block set to
|
|
934
|
+
`null` (`{"graph":null}`) removes it. `graph:` now keeps only the keys
|
|
935
|
+
someone set (it used to write every default), so setting a pref and
|
|
936
|
+
clearing it again leaves user.yml byte-identical.
|
|
937
|
+
- **Edge page: the cert-names card under tls `files`** (0.3.0-dev.32, from a
|
|
938
|
+
consumer) reads "Names the included certs should cover": Den issues
|
|
939
|
+
nothing there. mkcert keeps "Certificate names (planned)".
|
|
940
|
+
|
|
941
|
+
- **The pages' output panes are gone; their runs are tabs in the runs
|
|
942
|
+
dock** (0.3.0-dev.30, R16). Local Dev's output panel (one tab per
|
|
943
|
+
system, log tails, the dismissed tabs per workspace), Services' output
|
|
944
|
+
pane, Tests' output pane and its service command strip (Stop / Detach is
|
|
945
|
+
now a button in the dock's bar while the command runs), and Releases'
|
|
946
|
+
deploy log (each deploy session is a tab: selecting one attaches its
|
|
947
|
+
log, its × closes the session; the session chips are gone) all write to
|
|
948
|
+
the dock. The output lines are the same text with the same line classes;
|
|
949
|
+
k6's progress lines still update in place, Local Dev still keeps its
|
|
950
|
+
last 1000 lines per tab. Auto-scroll now pauses on every page when you
|
|
951
|
+
scroll up (Local Dev's rule).
|
|
952
|
+
- **The runs dock starts collapsed, and stays as you leave it** (0.3.0-dev.30,
|
|
953
|
+
R16a, Ward QA 2026-11-05). A run starting never opens it: its tab
|
|
954
|
+
appears and the Runs chip pulses, and you open it (Show runs, the chip or
|
|
955
|
+
a tab). Only an action whose whole point is output opens it: Logs on a
|
|
956
|
+
service (Services) and a card's output in Local Dev's graph. Open or
|
|
957
|
+
closed and its height are user prefs (`shell.dock`, `shell.dockHeight`
|
|
958
|
+
in `/api/settings`, the Settings page's Navigation row "Open the runs
|
|
959
|
+
dock"), so they hold across pages, reloads and browsers; nothing is kept
|
|
960
|
+
per browser tab any more. Dragging its top edge stops at half the window.
|
|
961
|
+
- **Settings: the search and the category list stay put** (0.3.0-dev.30,
|
|
962
|
+
R21a, Ward QA 2026-11-05). The page header with its search box and the
|
|
963
|
+
category list on the left are sticky while the rows scroll; a category
|
|
964
|
+
click scrolls its section to just below the header.
|
|
965
|
+
|
|
966
|
+
- **Existing edge configs render exactly as before** (0.3.0-dev.25). The
|
|
967
|
+
legacy keys map to `edge.tls` (EXT-1 rule 6): `issue_cert: false` is
|
|
968
|
+
`files`, `caroot` is `mkcert` with `ca`, and an `edge:` block with
|
|
969
|
+
neither is `mkcert` with the edge's own CA. A test renders each shape
|
|
970
|
+
(and its explicit `edge.tls` form) and compares the Traefik dynamic and
|
|
971
|
+
compose files, cert names and paths, route URLs and the mkcert call
|
|
972
|
+
byte for byte with 0.3.0-dev.24's own output. Under `files` with no `ca`,
|
|
973
|
+
`edge trust` now refuses (it minted a CA that signs nothing the edge
|
|
974
|
+
serves); with a `ca` it installs that CA, as before.
|
|
975
|
+
- **No built-in secret locations** (0.3.0-dev.23). Den no longer assumes a
|
|
976
|
+
vault, item, field, key or account of its own: the default 1Password
|
|
977
|
+
vault, developer-keys item and sudo field, the default account domain, and
|
|
978
|
+
the flat `<PREFIX>_LOCAL_ADMIN_PWD` key that doppler and env used for the
|
|
979
|
+
saved sudo password are gone. The saved sudo password and the journeys
|
|
980
|
+
agent key come only from `secrets.refs.sudo` and
|
|
981
|
+
`secrets.refs.journeys_agent_key`; without them each says "not configured:
|
|
982
|
+
set secrets.provider and secrets.refs.<name> in config/<cli>.yml" and
|
|
983
|
+
runs no CLI (the interactive unlock still works). A `1password`
|
|
984
|
+
distribution with no refs gets one warning in the web server's startup
|
|
985
|
+
log and in `validate`. The 1Password developer-keys setup writes to
|
|
986
|
+
`secrets.developer_keys: { vault, item }` (else the vault and item of
|
|
987
|
+
`refs.sudo`). See Migrations.
|
|
988
|
+
- **`make up` / `make web` check secrets only when `secrets.required` is
|
|
989
|
+
set** (0.3.0-dev.23). Otherwise the gate passes silently, whatever the
|
|
990
|
+
provider, and never asks the backend. `make <cli>-setup-secrets` runs the
|
|
991
|
+
selected strategy's setup; with `none`, or no provider set, it says there
|
|
992
|
+
is nothing to set up and exits 0.
|
|
993
|
+
- **`/api/sudo-password/vault-status` is backend-neutral** (0.3.0-dev.23):
|
|
994
|
+
`{ backend, label, configured, backendAvailable, reason?, hasStored }`,
|
|
995
|
+
and the unlock form says "Save to <backend>". `load-from-vault` answers
|
|
996
|
+
`{ ok: false, reason: "not-configured", message }` with no `refs.sudo`.
|
|
997
|
+
- **A local-jwt signing key that is not found names its provider**
|
|
998
|
+
(0.3.0-dev.23): "... not found in env or the doppler secrets backend
|
|
999
|
+
(secrets.provider)". Its resolution is unchanged.
|
|
1000
|
+
|
|
1001
|
+
### Deprecated
|
|
1002
|
+
|
|
1003
|
+
- **The implicit mkcert default (set `edge.tls`)** (0.3.0-dev.25). An
|
|
1004
|
+
`edge:` block with no `tls`, `caroot` or `issue_cert` still means mkcert
|
|
1005
|
+
with a local CA, and warns once per run (`validate`, `doctor`, `edge up`,
|
|
1006
|
+
`stack up`): "edge.tls is not set; defaulting to mkcert with a local CA.
|
|
1007
|
+
Set edge.tls explicitly." `edge.caroot` and `edge.issue_cert` still work,
|
|
1008
|
+
and `validate` and `doctor` name the `edge.tls` line that says the same.
|
|
1009
|
+
- **The implicit doppler default (set `secrets.provider`)** (0.3.0-dev.23).
|
|
1010
|
+
An unset `secrets.provider` still means `doppler`, but only when something
|
|
1011
|
+
actually resolves a secret; the first time it does, Den warns once:
|
|
1012
|
+
"secrets.provider is not set; the implicit doppler default is deprecated
|
|
1013
|
+
and will be removed in a later minor. Set it explicitly: doppler,
|
|
1014
|
+
1password, env or none." A distribution that resolves no secret never
|
|
1015
|
+
runs `doppler` and never sees the warning.
|
|
1016
|
+
- **`opAvailable` in `/api/sudo-password/vault-status`** (0.3.0-dev.23):
|
|
1017
|
+
an alias of `backendAvailable`, removed in a later minor. Read
|
|
1018
|
+
`backendAvailable` (and `backend`, `reason`).
|
|
1019
|
+
|
|
1020
|
+
### Fixed
|
|
1021
|
+
|
|
1022
|
+
- **`web up` under an invalid config reports the server ready**
|
|
1023
|
+
(0.3.0-dev.42, a consumer's dev.41 finding). It waited for
|
|
1024
|
+
`/api/health`, which answers 422 by design then, and exited 1 with
|
|
1025
|
+
"web did not become ready" while the server ran. Readiness now asks
|
|
1026
|
+
`/api/server-info` (answered under any config; `/api/health` for a
|
|
1027
|
+
server too old for it), and the 422 "Config is invalid" page counts as
|
|
1028
|
+
the shell serving.
|
|
1029
|
+
- **No page throws under an invalid config** (0.3.0-dev.41, a consumer's
|
|
1030
|
+
dev.40 finding: "envs.map is not a function" on every page).
|
|
1031
|
+
`/api/environments` is config-free (it already fell back to `local`),
|
|
1032
|
+
the env picker and the Logs toolbar treat an error answer as an empty
|
|
1033
|
+
list, and a test loads every page with an invalid config and expects
|
|
1034
|
+
no uncaught error.
|
|
1035
|
+
- **A refused `edge down` says "not stopping it"**, not "not replacing
|
|
1036
|
+
it"; `edge` rejects an unknown flag (a mistyped `--take-over` is never
|
|
1037
|
+
ignored) (0.3.0-dev.41).
|
|
1038
|
+
- **A pre-dev.37 record whose port is held by another process** no
|
|
1039
|
+
longer warns "nothing running is it" before saying what holds the port
|
|
1040
|
+
(0.3.0-dev.41).
|
|
1041
|
+
- **Safety: an edge another state dir owns is never replaced**
|
|
1042
|
+
(0.3.0-dev.40, P1). The edge's container and Compose project are named
|
|
1043
|
+
by `edge.org`, so a scratch root with its own `DEN_EDGE_DIR` but the real
|
|
1044
|
+
`edge.org` recreated the real edge with its scratch certs and routes
|
|
1045
|
+
(`stack up` brings the edge up first). The edge container now carries
|
|
1046
|
+
`den.edge.root=<realpath of its state dir>` and `den.edge.org=<org>`, and
|
|
1047
|
+
`edge up`, `stack up`/`restart` (their implicit edge up) and `edge down`
|
|
1048
|
+
touch an existing `<org>-edge` only when its `den.edge.root` is their own
|
|
1049
|
+
state dir. Otherwise they refuse before writing or starting anything,
|
|
1050
|
+
exit 1: "edge <org>-edge belongs to <its dir>; not replacing it". An
|
|
1051
|
+
unlabelled (pre-dev.40) edge is adopted only from the default state dir,
|
|
1052
|
+
never under a `DEN_EDGE_DIR` that overrides it (the refusal names its
|
|
1053
|
+
mounts' dir when known). Under such an override a refusal also warns:
|
|
1054
|
+
"Give this root a distinct edge.org." `edge up --take-over` is the only
|
|
1055
|
+
override, and nothing implies it. The routes (the dynamic files) are
|
|
1056
|
+
byte-identical. See "The edge and TLS" in docs/distributions.md.
|
|
1057
|
+
- **A recreated edge keeps running stacks routed** (0.3.0-dev.40, P1). A
|
|
1058
|
+
new or restarted edge container starts on its own network only, so every
|
|
1059
|
+
running stack's routes answered 502 until that stack's next `stack up`.
|
|
1060
|
+
After `edge up` (and `stack up`/`restart`'s implicit edge up) the edge
|
|
1061
|
+
now rejoins the routed networks of each stack that has its routes file
|
|
1062
|
+
(`dynamic/stack-<project>.yml`) in this edge's state dir and whose
|
|
1063
|
+
Compose project is running, with one line per network ("rejoined
|
|
1064
|
+
<network> (<project>)"). Stacks that are down are skipped; a network the
|
|
1065
|
+
edge is on already is left alone; a project with no routes file in this
|
|
1066
|
+
state dir is never touched. `stack up` connects through the same helper.
|
|
1067
|
+
- **`validate` reports an org config error** (0.3.0-dev.40, a consumer's
|
|
1068
|
+
0.2.8 nit): each problem with its file and key path, exit 1; the
|
|
1069
|
+
manifests are still checked.
|
|
1070
|
+
- **The scoped `manifest_validate` refusal names the whole path**
|
|
1071
|
+
(0.3.0-dev.40, a consumer's 0.2.8 nit): it was cut at 80 characters,
|
|
1072
|
+
mid-filename. Up to 160 it is whole; a longer one keeps its start and its
|
|
1073
|
+
filename and elides the middle with "…".
|
|
1074
|
+
- **A named `up <repo>/<svc>` whose port is held refuses before spawning**
|
|
1075
|
+
(0.3.0-dev.40, a consumer's dev.39 finding): it started the server once,
|
|
1076
|
+
which died on EADDRINUSE, before refusing. It now refuses as the
|
|
1077
|
+
repo-level up does ("<repo>/<svc> not started"), with or without a
|
|
1078
|
+
pre-dev.37 record. And a listener in the repo but in Den's own process
|
|
1079
|
+
group is said as that ("in Den's own process group; not adopted"), not
|
|
1080
|
+
as "outside <repoPath>".
|
|
1081
|
+
- **Graph cards** (0.3.0-dev.40, DOC-1): a service card's red "down"
|
|
1082
|
+
border goes when it recovers (only repo cards toggled it); before the
|
|
1083
|
+
first health round cards say "checking…", as the Workspaces table does,
|
|
1084
|
+
not "no health check"; the details panel's Opens link follows the edge's
|
|
1085
|
+
TLS (`http://` and the http port for `tls: none`; it always said
|
|
1086
|
+
`https://`).
|
|
1087
|
+
- **Records from before 0.3.0-dev.37 reach adoption from the CLI**
|
|
1088
|
+
(0.3.0-dev.39, a consumer's dev.38 finding). dev.38 decided right when
|
|
1089
|
+
asked, but the CLI's paths never asked. Every up, down and healthy-skip
|
|
1090
|
+
now reads a service's record through one accessor
|
|
1091
|
+
(`settledDaemonRecord`) that settles a pre-dev.37 record first: adopted
|
|
1092
|
+
(its group recorded) or dropped (said, nothing signalled).
|
|
1093
|
+
- `up <repo>` on a healthy service attached to the old record and left
|
|
1094
|
+
it without a group, so a later `down` could not stop it. It now adopts
|
|
1095
|
+
the running process ("adopted pre-dev.37 process (pid …, port …)"),
|
|
1096
|
+
starts nothing, and `down <repo>` stops it.
|
|
1097
|
+
- `down <repo>/<svc>` of a service with an up of its own pointed at the
|
|
1098
|
+
repo's down ("it is stopped by repox's down") and stopped nothing. Its
|
|
1099
|
+
down is its own: the group its up recorded.
|
|
1100
|
+
- A healthy service with no record whose port is held by a process in
|
|
1101
|
+
its repo is adopted the same way; one held from outside the repo is
|
|
1102
|
+
said ("port N is in use by pid … (cwd …), outside …: left alone"),
|
|
1103
|
+
never claimed or signalled, and nothing is started on it (a named
|
|
1104
|
+
target fails; a dependency warns).
|
|
1105
|
+
- An up whose process exits non-zero during startup (a second server
|
|
1106
|
+
that died on EADDRINUSE) keeps no record and no process group, so a
|
|
1107
|
+
later `down` never signals a dead group while the real listener runs.
|
|
1108
|
+
- The CLI prints what was adopted, dropped or found busy; before, those
|
|
1109
|
+
lines only flashed in the spinner.
|
|
1110
|
+
- `down -a` warns about records no active workspace reaches ("… is in no
|
|
1111
|
+
active workspace: down -a left it alone") and leaves them alone, where
|
|
1112
|
+
it skipped them silently.
|
|
1113
|
+
- **`restart` of a service with only an `up` works** (0.3.0-dev.39, a
|
|
1114
|
+
consumer's dev.38 finding). `restart repox/web` failed with `command
|
|
1115
|
+
"restart" not found` and left the old group running. With no restart or
|
|
1116
|
+
down command, a restart stops the recorded group, then runs the up
|
|
1117
|
+
detached, as `down` then `up` would.
|
|
1118
|
+
- **`down <repo>` and `down -a` stop a repo's per-service daemons**
|
|
1119
|
+
(0.3.0-dev.38, a consumer's dev.37 finding). A repo with services of
|
|
1120
|
+
their own (`up repox` started `repox/web` and `repox/api`, each with
|
|
1121
|
+
its own process group) and no down of its own printed "down repox
|
|
1122
|
+
(nothing to stop)" and left both running. A repo-level down with no
|
|
1123
|
+
down command now stops every service record of the repo through the
|
|
1124
|
+
per-record path (its refcount, its down command or its recorded group);
|
|
1125
|
+
`down -a` does it for each repo of the workspace. The mirror of
|
|
1126
|
+
dev.35's `up` multi-service fix.
|
|
1127
|
+
- **Records from before 0.3.0-dev.37 are adopted or dropped, never
|
|
1128
|
+
trusted blindly** (0.3.0-dev.38, a consumer's dev.37 finding). A record
|
|
1129
|
+
with no process group made `up` print "✔ up repox/api" while nothing ran,
|
|
1130
|
+
and `down` give up on it. One rule now serves `up` and `down`: the
|
|
1131
|
+
listener on the service's known port (its `port`, the port in its
|
|
1132
|
+
health URL, or `--port`/`-p` in its up command) whose cwd is inside the
|
|
1133
|
+
repo is adopted (its process group is recorded: "adopted pre-dev.37
|
|
1134
|
+
process (pid N, port P)"); else a recorded pid whose group shows every
|
|
1135
|
+
token of the up command (leading `VAR=value` and an `npx`/`npm exec`
|
|
1136
|
+
prefix stripped) is adopted; else the record is stale: warned, dropped,
|
|
1137
|
+
nothing signalled. `up` then reports an adopted service already running
|
|
1138
|
+
and starts a stale one; `down` stops an adopted one through the
|
|
1139
|
+
TERM→KILL group path. A port held by a process outside the repo is
|
|
1140
|
+
named and left alone.
|
|
1141
|
+
- **A restart records only the up** (0.3.0-dev.38). A service with a down
|
|
1142
|
+
and an up but no restart ran one detached `sh -c "<down> && <up>"`, so
|
|
1143
|
+
its recorded process group held the down too. It is now the down step,
|
|
1144
|
+
then a detached up step, and the group is the up's.
|
|
1145
|
+
|
|
1146
|
+
- **Graph: a selected card's flowing edges are visible** (0.3.0-dev.37,
|
|
1147
|
+
Ward QA of dev.35). The marching dashes were drawn in the same colour on
|
|
1148
|
+
a full-strength line, so they barely showed. A lit edge's line is now a
|
|
1149
|
+
faint track in its kind's colour and the dashes run wider on top of it;
|
|
1150
|
+
under reduced motion the lit edge stays solid and thicker, as before.
|
|
1151
|
+
- **Test workers inherit no credentials** (0.3.0-dev.37). `tests/setup.ts`
|
|
1152
|
+
strips credential-shaped variables (by name, family or token shape) from
|
|
1153
|
+
the environment before any suite runs, so a failing assertion on a
|
|
1154
|
+
spawn env can no longer print a developer's tokens.
|
|
1155
|
+
- **`down` stops what a detached `up` started** (0.3.0-dev.37, a
|
|
1156
|
+
consumer's dogfood finding). `<cli> down <repo>` for a service with no
|
|
1157
|
+
down command and no container printed ✔ and stopped nothing: daemon
|
|
1158
|
+
records kept no process. A detached `up` now records its process group
|
|
1159
|
+
(`pgid`, and the leader's start time as `pgidStartedAt`) in the daemon
|
|
1160
|
+
record, and `down` sends the group SIGTERM, waits up to 3s
|
|
1161
|
+
(`DEN_DOWN_GRACE_MS`), then SIGKILL. Before each signal it checks the
|
|
1162
|
+
pid still belongs to the recorded process (same start time), so a
|
|
1163
|
+
reused pid is never signalled: that service had already exited, and
|
|
1164
|
+
`down` says so and leaves the process alone. After a declared down
|
|
1165
|
+
succeeds, a recorded group still running is stopped too. When `down`
|
|
1166
|
+
cannot stop what it knows is running (a record written before dev.37,
|
|
1167
|
+
with no group; a group that survives SIGKILL), it warns, and an explicit
|
|
1168
|
+
`down <repo>` exits non-zero (a workspace-wide `down` warns and goes
|
|
1169
|
+
on). With nothing recorded, the CLI says "nothing to stop" instead of ✔.
|
|
1170
|
+
`down --dry-run` lists a service with a recorded group as one it stops.
|
|
1171
|
+
- **A compose-stack repo's Start and Restart work on the Workspaces page**
|
|
1172
|
+
(0.3.0-dev.37, a consumer's dev.35 finding). The repo row's buttons
|
|
1173
|
+
were disabled, "has no up command", for a repo whose up is its compose
|
|
1174
|
+
stack's. `/api/services` now gives each local service its effective
|
|
1175
|
+
commands (a stack's synthesized up/down/restart included) and
|
|
1176
|
+
`upTargets`: what `<cli> up <repo>` starts (`resolveUpTargets`), as the
|
|
1177
|
+
table's addresses. The repo row's Start and Restart are enabled when it
|
|
1178
|
+
is non-empty and run exactly those addresses.
|
|
1179
|
+
- **The Workspaces table says "checking…" until the first health round
|
|
1180
|
+
lands** (0.3.0-dev.37). Before it, repo and service health cells read
|
|
1181
|
+
"unknown (not checked)" or "no health check".
|
|
1182
|
+
- **Find shows only the sub-rows it matches** (0.3.0-dev.37). A Find that
|
|
1183
|
+
matched one service of a repo opened every one of the repo's sub-rows;
|
|
1184
|
+
now only the matching ones show under their repo (a match on the repo
|
|
1185
|
+
itself still shows them all).
|
|
1186
|
+
|
|
1187
|
+
- **`<cli> up <repo>` no longer fails fast on a multi-service dependency**
|
|
1188
|
+
(0.3.0-dev.35; backported to 0.2.8). A dependency with no `up` of its
|
|
1189
|
+
own but with per-service ups (two services, each with its `up`) was run
|
|
1190
|
+
as the repo, `command "up" not found for service "<repo>"`, and the run
|
|
1191
|
+
aborted before the repo you asked for. `up <repo>` and `up -a` now make
|
|
1192
|
+
one decision (`resolveUpTargets`): a repo with its own `up` starts as
|
|
1193
|
+
before; one with none but per-service ups starts its services (all of
|
|
1194
|
+
them, or the ones its dependents name in `depends_on`); only one with
|
|
1195
|
+
no `up` at all is skipped, said as `-a` says it ("Skipping (no local up
|
|
1196
|
+
command): …"), and nothing waits on it. A dependency whose `up` fails
|
|
1197
|
+
still fails fast.
|
|
1198
|
+
|
|
1199
|
+
- **The graph fits on first paint** (0.3.0-dev.34, a consumer's dogfood
|
|
1200
|
+
finding). With "All active", `/graph` opened at 60% with 17 of 31 cards
|
|
1201
|
+
outside the stage at 1440×900 (23 at 1024×768) until Fit was clicked:
|
|
1202
|
+
the org frames land after the first draw, the graph was drawn again,
|
|
1203
|
+
and only the first draw was fitted (a render a newer one overtook could
|
|
1204
|
+
also fit the stale layout). The view now fits once the final layout is
|
|
1205
|
+
drawn, and again whenever the set of cards changes, unless you zoomed
|
|
1206
|
+
or panned since. Local Dev's per-workspace graph likewise.
|
|
1207
|
+
- **The Workspace picker on `/graph` replaces `?ws=`** (0.3.0-dev.34, a
|
|
1208
|
+
consumer's dogfood finding). On `/graph?ws=w2`, "All active" changed the
|
|
1209
|
+
pill but the URL kept `?ws=w2`, which wins, so the header and the graph
|
|
1210
|
+
stayed on w2, after a reload too. A choice there now drops `?ws=` (All
|
|
1211
|
+
active) or sets it (a workspace), replacing the history entry, and the
|
|
1212
|
+
pill shows the workspace a fresh `?ws=` names: the pill, URL, the
|
|
1213
|
+
header's line and the graph agree.
|
|
1214
|
+
- **The runs dock is a tablist only while it holds tabs** (0.3.0-dev.34).
|
|
1215
|
+
With no runs, `.den-runs-l` was a `role=tablist` holding a "No runs"
|
|
1216
|
+
span and no tab; the role and its label come back with the first run.
|
|
1217
|
+
- **`theme-switch-isolation` is deterministic under load** (0.3.0-dev.34;
|
|
1218
|
+
it failed 5/6 then 1/6 on a busy machine). Its file snapshot covered the
|
|
1219
|
+
throwaway HOME, where a container runner's CLI that the health check
|
|
1220
|
+
calls writes its own update hint on its own schedule; it now covers
|
|
1221
|
+
Den's files (the distribution and `DEN_HOME`). The server gets a port
|
|
1222
|
+
from the OS on localhost (not a random pick that could land on another
|
|
1223
|
+
session's server), and readiness waits for the server whose
|
|
1224
|
+
`/api/server-info` names this distribution's root.
|
|
1225
|
+
|
|
1226
|
+
- **Stale daemon records are dropped** (0.3.0-dev.33, a consumer's
|
|
1227
|
+
dogfood finding). A record whose repo had been relocated (no pid,
|
|
1228
|
+
`containers: []`) was kept forever because its address still resolved,
|
|
1229
|
+
and the Runs dock showed a permanent tab. The startup sweep and the
|
|
1230
|
+
health reaper now drop a record of this root's, or an unstamped one,
|
|
1231
|
+
whose `repoPath` is not on disk, or whose `repoPath` is no longer where
|
|
1232
|
+
the service resolves and none of its candidate containers (captured,
|
|
1233
|
+
or the dev-op's at its old path) is running; the relocated checkout's
|
|
1234
|
+
own container, which carries the repo's name, does not count.
|
|
1235
|
+
Another root's records are never touched, and a record that resolves
|
|
1236
|
+
to its own checkout with nothing to verify is still kept (it may be a
|
|
1237
|
+
live non-opctl process). Removals are logged with the sweep's and the
|
|
1238
|
+
reaper's lines.
|
|
1239
|
+
- **Graph group boxes fit their content** (0.3.0-dev.33, a consumer's
|
|
1240
|
+
dogfood finding). On `/graph` a repo box with one row of cards drew
|
|
1241
|
+
about twice the height it needs: top-down, ELK reads a compound node's
|
|
1242
|
+
minimum size in its own layout axes, so the repo box's minimum width
|
|
1243
|
+
(386px) became its height. The minimum is passed in that order now; the
|
|
1244
|
+
boxes are their padding plus their cards, and keep the header's width.
|
|
1245
|
+
- **Fit keeps the graph above the legend and the zoom group**
|
|
1246
|
+
(0.3.0-dev.33, a consumer's dogfood finding). After Fit, a legend on
|
|
1247
|
+
two lines covered the last group's bottom ~10px. Fit's height now leaves
|
|
1248
|
+
out the overlays' measured height (and a gap), so no card or group
|
|
1249
|
+
border sits under them.
|
|
1250
|
+
|
|
1251
|
+
- **CA trust on a fronted edge reads "not needed: fronted"** (0.3.0-dev.32,
|
|
1252
|
+
from a consumer). With `edge.publish: false` a proxy in front owns
|
|
1253
|
+
browser trust (BOOT-1), so the Edge page's CA trust tile is neutral
|
|
1254
|
+
instead of a red "not trusted", and `edge status` says the same.
|
|
1255
|
+
- **Fit follows the graph's size on maximize and restore** (0.3.0-dev.32,
|
|
1256
|
+
R23, from a consumer). Fit stopped at 35%, so a big graph at 1440×900 read
|
|
1257
|
+
35% maximized or not, and didn't fit either way. Fit now fits the whole
|
|
1258
|
+
graph, by width and height, down to 10% (zooming out goes as far as
|
|
1259
|
+
that), and a fitted view refits when the graph is maximized or restored.
|
|
1260
|
+
|
|
1261
|
+
- **A seed without a terminal no longer resets a workspace that has
|
|
1262
|
+
entries** (SEED-0, 0.3.0-dev.29; also 0.2.7). `<cli> init` and
|
|
1263
|
+
`<cli> workspace seed` from a script or `make` replaced every entry of the
|
|
1264
|
+
default (or blueprint) workspaces with managed clones, dev-managed
|
|
1265
|
+
checkouts included, without asking. Without a TTY they now refuse (exit 1,
|
|
1266
|
+
nothing written) and name `--reset`; on a TTY they still ask, default No.
|
|
1267
|
+
`init --reset` and `workspace seed --reset` reset as before. A script that
|
|
1268
|
+
relied on the silent reset must pass `--reset`.
|
|
1269
|
+
- **`<cli> init` in the distribution root seeds through a symlinked path**
|
|
1270
|
+
(0.3.0-dev.29): it compared paths without resolving links and ran the
|
|
1271
|
+
manifest scaffolder instead.
|
|
1272
|
+
|
|
1273
|
+
- **`create`'s "Next:" hint names an absolute path** (0.3.0-dev.26) when the
|
|
1274
|
+
new directory is not under the cwd, instead of `cd ../../../..`.
|
|
1275
|
+
|
|
1276
|
+
- **A fresh distribution runs no gh for a placeholder org** (0.3.0-dev.25,
|
|
1277
|
+
from a consumer's dev.24 run): `den create` wrote `github_org:
|
|
1278
|
+
your-github-org`, and Local Dev then ran `gh repo list your-github-org`
|
|
1279
|
+
on every load. The scaffold now leaves `github_org` commented out, with
|
|
1280
|
+
an example. With `github_org` unset or still a placeholder
|
|
1281
|
+
(`your-github-org`, `your-org`, `<your-org>`, `example-org`) GitHub is
|
|
1282
|
+
"not configured": `/api/github/repos` is empty and runs no gh (no
|
|
1283
|
+
fallback to catalog owners or a git remote), and `register`'s GitHub
|
|
1284
|
+
list ignores a placeholder.
|
|
1285
|
+
- **The lock's not-configured note names your config file** (0.3.0-dev.25):
|
|
1286
|
+
with no `secrets.refs.sudo`, the admin-password popover says "Saving the
|
|
1287
|
+
password is off: set secrets.provider and secrets.refs.sudo in
|
|
1288
|
+
config/acme.yml.", the distribution's real file, from the new
|
|
1289
|
+
`configFile` field of `/api/sudo-password/vault-status`.
|
|
1290
|
+
- **`register` with no terminal exits cleanly** (0.3.0-dev.25): with stdin
|
|
1291
|
+
closed or piped, the interactive `register` crashed with
|
|
1292
|
+
ERR_USE_AFTER_CLOSE. It now exits 2 (a usage error, since 0.2.8's
|
|
1293
|
+
check; it exited 1 before): "register needs a terminal (stdin is not a
|
|
1294
|
+
TTY)", naming the non-interactive form, `register <path> [--name
|
|
1295
|
+
<repo>] [--gh <owner/repo>]`.
|
|
1296
|
+
- **`bin/den` sets no backend account** (0.3.0-dev.24, from a consumer's
|
|
1297
|
+
dev.23 run): the launcher exported `OP_ACCOUNT` with a placeholder
|
|
1298
|
+
domain for every command, whatever the secrets backend, so it reached
|
|
1299
|
+
the web server even under doppler. It now adds nothing; the 1password
|
|
1300
|
+
strategy sets it from `secrets.onepassword.account`, or the caller's own
|
|
1301
|
+
is kept.
|
|
1302
|
+
- **`backendAvailable` is true for a usable backend even with no sudo ref**
|
|
1303
|
+
(0.3.0-dev.24): dev.23 reported false whenever `secrets.refs.sudo` was
|
|
1304
|
+
unset, so the deprecated `opAvailable` alias changed meaning. With no
|
|
1305
|
+
ref it now reflects detect() (a PATH check, nothing spawned) for an
|
|
1306
|
+
explicitly chosen backend; `configured: false` says the ref is missing.
|
|
1307
|
+
An unset provider and `none` still report false and are never probed.
|
|
1308
|
+
- **Raster marks are scaled to fit, never cropped** (0.3.0-dev.23).
|
|
1309
|
+
- **Den adds no 1Password account to anything it starts** (0.3.0-dev.23).
|
|
1310
|
+
The web server daemon (`web up`, `web restart`) was always given
|
|
1311
|
+
`OP_ACCOUNT`, with a built-in domain when unset, and `den.mk` exported
|
|
1312
|
+
one to every recipe. Now only the `1password` strategy gives one, from
|
|
1313
|
+
`secrets.onepassword.account` or your own `OP_ACCOUNT`; no other backend
|
|
1314
|
+
gives any, and `den.mk` sets none.
|
|
1315
|
+
- **1Password make targets exist only when 1Password is selected**
|
|
1316
|
+
(0.3.0-dev.23). `make <cli>-setup-1password` and `make auth-configure`
|
|
1317
|
+
moved to `make/secrets-1password.mk`, which `den.mk` includes only when
|
|
1318
|
+
`secrets.provider` (or `<PREFIX>_SECRETS_PROVIDER`) is `1password`; every
|
|
1319
|
+
other distribution no longer has them.
|
|
1320
|
+
- **"Register a managed repo" never lists the engine's owner's repos**
|
|
1321
|
+
(0.3.0-dev.23). With no `github_org`, the org fell back to the git remote
|
|
1322
|
+
of the engine's own checkout, so a linked or git-installed engine listed
|
|
1323
|
+
its owner's repos for an unrelated project. The fallback is now the
|
|
1324
|
+
distribution's own checkout (only when it is the top of its own repo),
|
|
1325
|
+
else the "set github_org" error. The 1Password setup scripts likewise
|
|
1326
|
+
read `developer_keys.env_file` and write `admin-auth-<env>.json` under the
|
|
1327
|
+
distribution root, not the engine's.
|
|
1328
|
+
- **Settings keeps your pick marked while it scrolls** (0.3.0-dev.22, SA's
|
|
1329
|
+
dev.21 run): a click or Enter on a category no longer walks
|
|
1330
|
+
`aria-current` through every category the smooth scroll passes. Your
|
|
1331
|
+
pick holds until the scroll ends (`scrollend`, else once it is still,
|
|
1332
|
+
within about a second); then the scroll-spy takes over again.
|
|
1333
|
+
- **Below 480px the top bar shows only the mark** (0.3.0-dev.22, SA's
|
|
1334
|
+
dev.21 run): the product name is visually hidden there and stays the
|
|
1335
|
+
brand link's accessible name; from 480px it truncates as in dev.21.
|
|
1336
|
+
- **0.2.6 is in the 0.3 line** (0.3.0-dev.21): `type: container` health
|
|
1337
|
+
checks, the server identity check behind `web restart` and `web down`,
|
|
1338
|
+
the MCP server's identity, and `service install --plan`. See 0.2.6 below.
|
|
1339
|
+
- **Settings marks the category you picked** (0.3.0-dev.21, SA's dev.20
|
|
1340
|
+
run): `/settings#distribution`, or a click on the last short section,
|
|
1341
|
+
marked the one before it as current, because the page could not scroll
|
|
1342
|
+
far enough for the section to reach the top. At the end of the page,
|
|
1343
|
+
your pick wins if it is in view.
|
|
1344
|
+
- **"IDE for this workspace" saves on change** (0.3.0-dev.21), like every
|
|
1345
|
+
other row; its Save button is gone.
|
|
1346
|
+
- **The top bar fits at 400px** (0.3.0-dev.21): a long product name
|
|
1347
|
+
truncates and the stale-server pill shows only its ⚠ (named "Stale
|
|
1348
|
+
server: restart"), so the page no longer scrolls sideways.
|
|
1349
|
+
- **Status drawer rows are named by service, state and latency**
|
|
1350
|
+
(0.3.0-dev.21), e.g. "shop/db, healthy, 9 ms". What the check ran is
|
|
1351
|
+
the row's description and tooltip, not its name.
|
|
1352
|
+
- **The rail's Settings item opens Settings** (0.3.0-dev.19): in dev.18 it
|
|
1353
|
+
went to `/local-dev?settings=1`, which nothing read, so it landed on
|
|
1354
|
+
Local Dev. It now goes to `/settings`, and the old URL redirects there.
|
|
1355
|
+
Plain `/settings` opens at the top, on the title and the search box; a
|
|
1356
|
+
link that names a category (`/settings#editor`) scrolls to it and puts
|
|
1357
|
+
focus on its heading.
|
|
1358
|
+
- **The rail's × keeps focus in the rail** (0.3.0-dev.19, SA's dev.18
|
|
1359
|
+
run), as Delete does: the next pinned row, else the previous one, else
|
|
1360
|
+
the first item (Enter on "Unpin <id>" dropped focus to the top of the
|
|
1361
|
+
document). One function for both.
|
|
1362
|
+
- **The narrow rail is a modal drawer** (0.3.0-dev.19, SA's dev.18 run).
|
|
1363
|
+
Below 720px, ☰ moves focus into the rail (the current page), makes the
|
|
1364
|
+
top bar and the page `inert` behind a scrim, and keeps Tab inside;
|
|
1365
|
+
Escape or a click on the scrim closes it, sets `aria-expanded` back and
|
|
1366
|
+
returns focus to ☰ (Escape did nothing before).
|
|
1367
|
+
- **Names screen readers read** (0.3.0-dev.19, SA's dev.18 run): a rail
|
|
1368
|
+
group is named by its label alone (`aria-labelledby` points at the label,
|
|
1369
|
+
not the heading with its tag; Chrome read "XGlobex"); a tagged menu
|
|
1370
|
+
row reads "default, focus", not "defaultfocus"; the ✓ on a checked menu
|
|
1371
|
+
row is drawn with no alt text (`aria-checked` says it); ◐ ▾'s glyphs are
|
|
1372
|
+
hidden from readers. A scope button's `aria-controls` names its menu
|
|
1373
|
+
only while the menu is open.
|
|
1374
|
+
- **A page registered before its group no longer warns** (0.3.0-dev.19,
|
|
1375
|
+
a consumer's dev.18 run). Queuing a page before its group is a legitimate
|
|
1376
|
+
order; the warning now waits until the queue has drained and a tick has
|
|
1377
|
+
passed after `den-shell:ready`, and fires only for a group that is still
|
|
1378
|
+
missing (the same for a setting and its category).
|
|
1379
|
+
- **Opening a run's output no longer hides the lowest graph cards**
|
|
1380
|
+
(0.3.0-dev.19, a consumer's dev.18 run). Automatic fits (first draw, layout
|
|
1381
|
+
changes, and the re-fit as the output panel opens) now show the whole
|
|
1382
|
+
graph whenever it fits at 35% zoom, the Fit button's limit; only a graph
|
|
1383
|
+
too big for that starts at its top at 60%. The panel already had its
|
|
1384
|
+
own room below the graph: stopping the fit at 60% cut the lowest cards
|
|
1385
|
+
off at the graph's bottom edge, where the panel begins, so a click
|
|
1386
|
+
landed on the panel.
|
|
1387
|
+
- **The ORG label is the org's name, not its id** (0.3.0-dev.18). With no
|
|
1388
|
+
`org.title` in the org config, the label was the id (`edge.org`). It is
|
|
1389
|
+
now `org.title`, then the `orgs:` label of `github_org` (the name the
|
|
1390
|
+
Environments page shows), then the id.
|
|
1391
|
+
- **A pin toggle follows the rail** (0.3.0-dev.18). Unpinning from the rail
|
|
1392
|
+
(× or Delete) left the Services card's ☆ and the graph panel's reading
|
|
1393
|
+
"Unpin X" (`aria-pressed=true`) until a reload.
|
|
1394
|
+
- **Delete on a pinned row keeps focus in the rail** (0.3.0-dev.18): on the
|
|
1395
|
+
next pinned row, else the previous one, else the first item (it fell to
|
|
1396
|
+
the top of the document).
|
|
1397
|
+
- **A pinned row is named by its id** (0.3.0-dev.18), not "shop-web×":
|
|
1398
|
+
the × is its own control, out of the tab order, named "Unpin
|
|
1399
|
+
shop-web", its glyph hidden from screen readers; the row has
|
|
1400
|
+
`aria-keyshortcuts="Delete"`.
|
|
1401
|
+
- **One main landmark per page** (0.3.0-dev.18). Every page's content is in
|
|
1402
|
+
`<main id="den-content">` (the skip link's target); the four pages that
|
|
1403
|
+
had a `<main>` of their own around part of it, and the two that had
|
|
1404
|
+
none, now have exactly one.
|
|
1405
|
+
- **A distribution group is named once** (0.3.0-dev.18): its tag is hidden
|
|
1406
|
+
from screen readers and read as the group's description ("from globex"),
|
|
1407
|
+
not "Globex globex".
|
|
1408
|
+
- **Registration mistakes warn** (0.3.0-dev.18): a non-number `order`, or
|
|
1409
|
+
a `registerNav` into a group not registered yet, logs a `console.warn`
|
|
1410
|
+
naming the field. The page waits for its group instead of being lost.
|
|
1411
|
+
- **Local Dev's output panel no longer covers graph cards** (0.3.0-dev.18).
|
|
1412
|
+
When the panel opened (a run's tab), the graph's viewport shrank but the
|
|
1413
|
+
graph kept its fit, so the lower cards sat under the panel and couldn't
|
|
1414
|
+
be clicked. A viewport that changes size re-fits the graph unless you
|
|
1415
|
+
have panned or zoomed it yourself.
|
|
1416
|
+
|
|
1417
|
+
- **An app is never painted down before its checks have answered**
|
|
1418
|
+
(0.3.0-dev.17). One cold load in twenty painted an app red for ~100ms:
|
|
1419
|
+
only its one down service had answered, and as the only row it read as
|
|
1420
|
+
the entry service. A poll that overlapped the first one started from
|
|
1421
|
+
nothing and drew what had landed. Until every check of a repo has
|
|
1422
|
+
answered (a pending check has), the repo is unknown (neutral, "still
|
|
1423
|
+
checking"); a repo the last complete cycle described keeps its earlier
|
|
1424
|
+
answers meanwhile. The rail's Services badge and pinned dots read the same
|
|
1425
|
+
rollup, so they count only completed downs.
|
|
1426
|
+
- **Shell accessibility** (0.3.0-dev.17): a "Skip to content" link comes
|
|
1427
|
+
first on every page; the rail is one tab stop (the current page) with
|
|
1428
|
+
↑/↓, Home and End moving within it; the Logs link's name is "Logs" or
|
|
1429
|
+
"Logs, 2 open", not "Logs0"; the Tests page's title is "<title> · Tests".
|
|
1430
|
+
- **"What's new" is a modal dialog, and reopens at the top** (0.3.0-dev.17).
|
|
1431
|
+
A reopen after scrolling kept the old position: the scroll was reset
|
|
1432
|
+
while the overlay was still hidden, where it doesn't stick; it is reset
|
|
1433
|
+
once the dialog shows. The box is `role="dialog"` with `aria-modal` and
|
|
1434
|
+
its heading as the label; it takes focus (the versions toggle, else Got
|
|
1435
|
+
it), keeps Tab inside, and gives focus back on close (to the version pill
|
|
1436
|
+
after a first-visit open). A browser test covers Escape, × and Got it,
|
|
1437
|
+
collapsed and expanded; CI installs Playwright's chromium for it.
|
|
1438
|
+
- **One bar on Logs** (0.3.0-dev.17). The cols/rows toggle moved into the
|
|
1439
|
+
Logs toolbar from a band of its own.
|
|
1440
|
+
- **One workspace control on Services** (0.3.0-dev.18). Services' own
|
|
1441
|
+
workspace select (beside the top bar's) is gone: the top bar's Workspace
|
|
1442
|
+
▾ says what the page lists; its status moved to the Output bar.
|
|
1443
|
+
- The appearance control (◐) sits at the header's far right on every page
|
|
1444
|
+
and never leaves the screen: the nav shrinks and scrolls and the health
|
|
1445
|
+
dots give way first (the Tests page's top bar overflowed at 1440 and 1024).
|
|
1446
|
+
- Org colors from the config are no longer text: the graph's org frames and
|
|
1447
|
+
the panel's breadcrumb label in `--text` with the color as a dot (they read
|
|
1448
|
+
at 2.5:1 on a light ground), and the frame's ground is neutral. A test
|
|
1449
|
+
bans a config color as `color:`.
|
|
1450
|
+
- The active workspace row in the Local Dev sidebar is a calm neutral tint
|
|
1451
|
+
in both modes (it was a loud accent tint in light mode).
|
|
1452
|
+
- `plugin list` with broken dirs in `plugins/` (0.3.0-dev.8, reported by
|
|
1453
|
+
consumers on dev.7) prints exactly one row for every directory there, every
|
|
1454
|
+
time. Before, it crashed with no rows when `plugins.lock.json` couldn't be
|
|
1455
|
+
parsed, skipped a symlink, and hid a dir whose lock entry named another
|
|
1456
|
+
id. `plugin validate` reports each of these too. Nothing in a plugin dir
|
|
1457
|
+
makes either command throw.
|
|
1458
|
+
- A dir name that isn't an id (such as `plugins/x/`) now gives the real
|
|
1459
|
+
rule: "2–40 characters: lowercase letters, digits or dashes, starting with
|
|
1460
|
+
a letter". A missing manifest is reported as well (0.3.0-dev.8).
|
|
1461
|
+
- A dir with no manifest reads "local (plugins/scratch): no den-plugin.json
|
|
1462
|
+
or den-theme.json, so it isn't a plugin" in `plugin list`. "Not valid
|
|
1463
|
+
under this <cli>: …" is now kept for a recognized plugin that fails
|
|
1464
|
+
validation (0.3.0-dev.8).
|
|
1465
|
+
- `theme list` still shows themes only, and still exits 0 when other plugin
|
|
1466
|
+
dirs are broken. It now adds one line, "N plugin dir(s) have errors: run
|
|
1467
|
+
`<cli> plugin list`" (0.3.0-dev.8).
|
|
1468
|
+
|
|
1469
|
+
- `theme list`'s footer counts every broken plugin dir, whether or not it
|
|
1470
|
+
is a row above, and gets the plural right: "1 plugin dir has errors" /
|
|
1471
|
+
"4 plugin dirs have errors: run `<cli> plugin list`" (0.3.0-dev.9; it
|
|
1472
|
+
counted only the hidden ones, as "N plugin dir(s)").
|
|
1473
|
+
- `plugin validate` says the same as `plugin list` for a dir with no
|
|
1474
|
+
manifest: "no den-plugin.json or den-theme.json, so it isn't a plugin",
|
|
1475
|
+
from one constant. `plugin list`'s and `theme list`'s id column fits the
|
|
1476
|
+
longest id, so rows stay aligned with ids up to 40 characters
|
|
1477
|
+
(0.3.0-dev.9).
|
|
1478
|
+
- **The header's health row no longer cuts repos off** (0.3.0-dev.9, a
|
|
1479
|
+
regression from the one-line header earlier in 0.3.0). The row was clipped
|
|
1480
|
+
silently: whole repos were hidden, a name could be cut mid-word, and a
|
|
1481
|
+
hidden repo that was down didn't show. It now:
|
|
1482
|
+
- orders problems first: down, then degraded, then healthy, then unknown
|
|
1483
|
+
(0.3.0-dev.11; dev.9 put unknown before healthy), within and across
|
|
1484
|
+
application boxes;
|
|
1485
|
+
- shows only items that fit whole;
|
|
1486
|
+
- ends with a "+N" chip for the rest, named "N more repos" and saying
|
|
1487
|
+
"+N · 1 down" when a hidden repo has a problem. The chip opens the full
|
|
1488
|
+
list; Escape closes it and returns focus to the chip.
|
|
1489
|
+
- fits again on resize, when fonts load, and when the rest of the header
|
|
1490
|
+
squeezes it.
|
|
1491
|
+
|
|
1492
|
+
An interim fix until the slice-3 status chip. The new module is
|
|
1493
|
+
`public/health-fit.js`.
|
|
1494
|
+
|
|
1495
|
+
- **One repo health rule for the header, the chip, the graph and
|
|
1496
|
+
`<cli> status`** (0.3.0-dev.10). The header row and `status` read only
|
|
1497
|
+
the entry service, so a repo whose web answered but whose mailpit
|
|
1498
|
+
container was gone read healthy. It sorted with the healthy repos and
|
|
1499
|
+
never reached the chip's count, while the graph showed the down service.
|
|
1500
|
+
All four now use one function, `public/health-rollup.js`, typed for
|
|
1501
|
+
TypeScript in `src/shared/health/rollup.ts`. Only checked signals count:
|
|
1502
|
+
- **down**: the entry service (the manifest `entrypoint`, else the
|
|
1503
|
+
service named like the repo, else the only one) is down;
|
|
1504
|
+
- **degraded**: another checked service is down;
|
|
1505
|
+
- **ok**: at least one signal is checked and none fails;
|
|
1506
|
+
- **unknown**: nothing is checked. It is neutral, never green, and sorts
|
|
1507
|
+
last, after healthy (0.3.0-dev.11). A service not yet probed is
|
|
1508
|
+
unknown. `status` keeps the graph's order.
|
|
1509
|
+
|
|
1510
|
+
A repo of 26 services with one probe is judged by that probe.
|
|
1511
|
+
- `status` prints one row per repo with its rolled-up state and names
|
|
1512
|
+
what is down, e.g. "degraded" with "mailpit down". It says "N not
|
|
1513
|
+
checked" when services aren't probed. An entrypoint-less repo is
|
|
1514
|
+
rolled up too, and `--verbose` still lists every service.
|
|
1515
|
+
- The graph's repo card shows degraded in `--warn`.
|
|
1516
|
+
- In the row, same-state repos sort the focused workspace's first, then
|
|
1517
|
+
by name. `/api/health` rows carry `focused`.
|
|
1518
|
+
- **The health row no longer hides everything behind one big application
|
|
1519
|
+
box** (0.3.0-dev.10).
|
|
1520
|
+
- When the boxes don't all fit, the row shows repos one by one. It places
|
|
1521
|
+
every down or degraded repo that fits first, and a problem that is too
|
|
1522
|
+
wide never blocks a later one. Then it adds the rest, in order.
|
|
1523
|
+
- The chip shows the richest text that fits ("+9 · 1 down, 1 degraded",
|
|
1524
|
+
then "+9 · 1 down"). Its name and tooltip always carry the full count.
|
|
1525
|
+
- The row now takes the header's free space. Before, it shrank to its
|
|
1526
|
+
content, and the page's own spacer took half of what was left.
|
|
1527
|
+
- **The health row is never an empty box on a cold load** (0.3.0-dev.11).
|
|
1528
|
+
Before the first health result (the application and editor lookups and
|
|
1529
|
+
the stream come first) the row painted nothing, and since dev.10 it takes
|
|
1530
|
+
the header's free space, so it read as a wide empty box with no refresh
|
|
1531
|
+
button. It now shows a neutral "…" and the refresh button at once. A fit
|
|
1532
|
+
that runs before the row or its items have a width keeps everything
|
|
1533
|
+
boxed and runs again next frame (`fitPlan` in `public/health-fit.js`).
|
|
1534
|
+
It fits again on the first data and when fonts load. The brand, version
|
|
1535
|
+
badge and pills beside the row never shrink for it.
|
|
1536
|
+
- **The health row's fit, checked against a real 6+3 workspace**
|
|
1537
|
+
(0.3.0-dev.11). Measured names of 71 to 182px: all nine flat need about
|
|
1538
|
+
1070px, so a 1002px row shows 7 and "+2", and a 1282px row shows both
|
|
1539
|
+
boxes whole. The fit was right; these numbers are now a test, with an
|
|
1540
|
+
11-repo 7+3+1 case for the flat fallback.
|
|
1541
|
+
- **The "+N" chip names a hidden problem in its text** (0.3.0-dev.12).
|
|
1542
|
+
With only room for the chip, a hidden degraded repo read "+11" with an
|
|
1543
|
+
amber ring, and "1 degraded" was only in its accessible name. The ladder
|
|
1544
|
+
is now "+11 · 1 down, 1 degraded", then the worst one alone ("+11 · 1
|
|
1545
|
+
down"), then compact ("+11 · ⚠2", 0.3.0-dev.13), down and degraded named
|
|
1546
|
+
the same way. Bare "+N" only when no
|
|
1547
|
+
hidden repo has a problem. The fit keeps room for that text
|
|
1548
|
+
(`fitPlan`'s `chipWidthOf`).
|
|
1549
|
+
- **Escape from the chip's list returns focus to the chip, even after a
|
|
1550
|
+
re-fit** (0.3.0-dev.12). A health tick or a font load while the list was
|
|
1551
|
+
open laid the row out again with a new chip node, so focus fell to the
|
|
1552
|
+
page (the breadcrumb). The chip is now one node across re-fits, keeps its
|
|
1553
|
+
focus, and stays the list's anchor; when a re-fit drops the chip, focus
|
|
1554
|
+
moves to the row's refresh button.
|
|
1555
|
+
- **The health row settles once on a fresh load** (0.3.0-dev.13). It
|
|
1556
|
+
reshuffled three or four times between 0.6s and 3.6s as streamed results
|
|
1557
|
+
arrived. Now a first cycle (a page load, an environment switch) keeps the
|
|
1558
|
+
"…" placeholder until the whole cycle is in (or 8s pass with a slow probe
|
|
1559
|
+
still out), then fits once. Later data repaints are debounced by 300ms;
|
|
1560
|
+
resize and font re-fits stay immediate (`paintGate` in
|
|
1561
|
+
`public/health-fit.js`).
|
|
1562
|
+
- **The refresh button is never clipped** (0.3.0-dev.13). At 124px,
|
|
1563
|
+
"+11 · 1 degraded" pushed ⟳ out of the row. The chip's ladder ends in a
|
|
1564
|
+
compact form, "+11 · ⚠1" (every hidden problem counted), used before the
|
|
1565
|
+
problem would be dropped; its name and tooltip keep the full text. If
|
|
1566
|
+
even that doesn't fit, the chip's text gives way, not ⟳. The chip is
|
|
1567
|
+
measured at its natural width.
|
|
1568
|
+
- **One shell for every page: a top bar and a left rail** (0.3.0-dev.14,
|
|
1569
|
+
shell slice 2). Every page had its own copy of the header, nav and header
|
|
1570
|
+
CSS (six copies; Environments loaded none of the header's behaviour).
|
|
1571
|
+
`public/shell.js` now draws one frame on every page, and the pages keep
|
|
1572
|
+
only their content:
|
|
1573
|
+
- **Top bar** (48px): the theme's mark and the title in the theme's
|
|
1574
|
+
display face (`var(--display)`; the pages had used the mono face), the
|
|
1575
|
+
version pill, the health row and chip (unchanged), the workspace focus
|
|
1576
|
+
pill, one env picker and ◐.
|
|
1577
|
+
- **Rail**: Local Dev, Services, Environments, Releases, Logs, Tests, the
|
|
1578
|
+
current page marked `aria-current="page"`. ↑/↓, Home and End move
|
|
1579
|
+
between pages; it collapses to icons below 1100px (labels stay for
|
|
1580
|
+
assistive tech). No horizontal page scroll at 1024.
|
|
1581
|
+
- **One env picker for the whole shell.** Tests, Services and Logs each
|
|
1582
|
+
had their own; the choice is now one control, remembered across pages.
|
|
1583
|
+
Local Dev and Releases keep checking local health whatever it says
|
|
1584
|
+
(`data-health-env`), and Releases' own selector is now labelled as the
|
|
1585
|
+
deploy target it is.
|
|
1586
|
+
- A page joins the shell with `<div id="den-shell" data-page="…">` and
|
|
1587
|
+
`<script src="/shell.js">`; `header.js` drives it (`envReady()`,
|
|
1588
|
+
`healthEnv()`).
|
|
1589
|
+
- **Header fixes** (0.3.0-dev.14): a long workspace name ends in an
|
|
1590
|
+
ellipsis with the full name in its tooltip, and names in the bar never
|
|
1591
|
+
wrap mid-word; the Tests page's "ENV" label no longer collides with its
|
|
1592
|
+
select (the picker is the shell's).
|
|
1593
|
+
- **Environments groups services under their org again** (0.3.0-dev.14).
|
|
1594
|
+
Every service showed under "no org": the page read each row's `org` from
|
|
1595
|
+
`/api/health`, which never sends one. It now takes each repo's org from
|
|
1596
|
+
`/api/orgs?repos=1`, like the graph.
|
|
1597
|
+
- **The first health paint waits for the whole cycle** (0.3.0-dev.14). A
|
|
1598
|
+
stream that broke after its first results counted as a finished cycle,
|
|
1599
|
+
so a cold load could paint a partial row. Only the server's `done` ends
|
|
1600
|
+
a cycle now; a broken stream is completed from `/api/health` (or the 8s
|
|
1601
|
+
cap paints what landed, and a check with no result is never shown as
|
|
1602
|
+
down). `healthCycle` in `public/health-fit.js`.
|
|
1603
|
+
- **The Theme menu says when your theme isn't in use** (0.3.0-dev.10). A
|
|
1604
|
+
stored theme that fails validation selects nothing, instead of looking
|
|
1605
|
+
like "Org default". One line explains: "Your theme <id> isn't valid
|
|
1606
|
+
(<first problem>), so <name> is in use", or "isn't installed". Picking
|
|
1607
|
+
any option replaces the stored choice.
|
|
1608
|
+
|
|
1609
|
+
### Migrations
|
|
1610
|
+
|
|
1611
|
+
- 0.3.0-dev.40, the edge's owner labels: plan one `edge up`. The edge's
|
|
1612
|
+
compose.yml gains `den.edge.root` and `den.edge.org` labels, so the first
|
|
1613
|
+
`edge up` (or `stack up`) after upgrading recreates the edge once, from
|
|
1614
|
+
its own state dir. Routes drop only while the container restarts, then
|
|
1615
|
+
stay up: the edge rejoins running stacks' networks. Run it once, from
|
|
1616
|
+
the edge's own dir, when a brief blip is acceptable. A scratch or test root that sets `DEN_EDGE_DIR` needs an
|
|
1617
|
+
`edge.org` of its own; with the real one it is refused.
|
|
1618
|
+
- 0.3.0-dev.40, commands that detach: a command other than up and restart
|
|
1619
|
+
that should keep running detached says `detachable: true`.
|
|
1620
|
+
- 0.3.0-dev.39, the UI language: nothing required. With no `language:`
|
|
1621
|
+
in `user.yml` the UI is English, as before. A distribution or plugin
|
|
1622
|
+
that adds its own words with `denShell.addMessages(lang, table)` can
|
|
1623
|
+
add `ja` and `es` tables the same way.
|
|
1624
|
+
- 0.3.0-dev.39, pre-dev.37 records: nothing required. Their next `up`,
|
|
1625
|
+
`down` or `restart` from the CLI or the web adopts or drops them (said).
|
|
1626
|
+
- 0.3.0-dev.38, the restart banner: **restart the web server once after
|
|
1627
|
+
installing.** The first upgrade from a server older than dev.38 can't
|
|
1628
|
+
show the "Den was updated" banner: the old server has no MD-2 code, so
|
|
1629
|
+
it keeps serving the new pages from the old engine until it restarts
|
|
1630
|
+
(`<cli> web restart`, or your service restart). From dev.38 on, the
|
|
1631
|
+
banner says when a restart is needed.
|
|
1632
|
+
- 0.3.0-dev.38, `/api` error bodies: **they changed from text to JSON.**
|
|
1633
|
+
Every `/api/*` error now answers `Content-Type: application/json` with
|
|
1634
|
+
`{ ok: false, error, code, requestId }`, where some routes sent
|
|
1635
|
+
`text/plain` (a message, or an empty body). A script that read the text
|
|
1636
|
+
should read `.error`; a status check needs nothing. Den's own pages and
|
|
1637
|
+
the MCP server read the new shape.
|
|
1638
|
+
- 0.3.0-dev.38, records from before dev.37: nothing required. A service
|
|
1639
|
+
still running from such a record is adopted on its next `up` or `down`
|
|
1640
|
+
(by its port, else its pid); one that can't be matched is dropped with a
|
|
1641
|
+
warning and never signalled.
|
|
1642
|
+
- 0.3.0-dev.37, `down` and process groups: nothing required. A service
|
|
1643
|
+
started by an older Den has no recorded group, so its first `down
|
|
1644
|
+
<repo>` after the upgrade (for a service with no down command and no
|
|
1645
|
+
container) warns, drops that record and exits non-zero: stop it
|
|
1646
|
+
yourself once; its next `up` records the group.
|
|
1647
|
+
- 0.3.0-dev.36, `stack_namespace`: optional, no change required. Without it
|
|
1648
|
+
every Compose project, container, volume and network keeps its name.
|
|
1649
|
+
`stack_namespace` in a blueprint needs Den 0.3.0-dev.36 or later: an
|
|
1650
|
+
older Den rejects the unknown key.
|
|
1651
|
+
- 0.3.0-dev.25, edge TLS. Nothing is required: every existing edge config
|
|
1652
|
+
renders as before (only `edge trust` under `issue_cert: false` with no
|
|
1653
|
+
`caroot` now refuses). To silence the warnings, say what your config already means,
|
|
1654
|
+
in the org config (`config/<cli>.yml`):
|
|
1655
|
+
- **No `tls`, `caroot` or `issue_cert`** (the implicit default):
|
|
1656
|
+
|
|
1657
|
+
```yaml
|
|
1658
|
+
edge:
|
|
1659
|
+
tls: { strategy: mkcert }
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
Or, if the edge needs no https, `tls: none`.
|
|
1663
|
+
- **`caroot: <ca>`**: replace it with
|
|
1664
|
+
|
|
1665
|
+
```yaml
|
|
1666
|
+
edge:
|
|
1667
|
+
tls: { strategy: mkcert, ca: <ca> }
|
|
1668
|
+
```
|
|
1669
|
+
|
|
1670
|
+
- **`issue_cert: false`** (with `include` and `mounts`): replace it with
|
|
1671
|
+
|
|
1672
|
+
```yaml
|
|
1673
|
+
edge:
|
|
1674
|
+
tls:
|
|
1675
|
+
strategy: files
|
|
1676
|
+
include: edge/dynamic # was edge.include
|
|
1677
|
+
mounts: { <host path>: <container path> } # was edge.mounts
|
|
1678
|
+
```
|
|
1679
|
+
|
|
1680
|
+
`include` and `mounts` may also stay where they were. With `caroot`
|
|
1681
|
+
too, move it to `ca: <ca>` under `tls`.
|
|
1682
|
+
- `edge.tls` beside `caroot` or `issue_cert` is an error: move them.
|
|
1683
|
+
- `github_org: your-github-org` from an earlier `den create`: set your
|
|
1684
|
+
real org, or delete the line.
|
|
1685
|
+
- 0.3.0-dev.23, secrets. Nothing is required of a distribution that uses
|
|
1686
|
+
no secrets; the YAML below goes in the org config (`config/<cli>.yml`).
|
|
1687
|
+
- **Relied on the old 1Password defaults** (the saved sudo password or
|
|
1688
|
+
the journeys key in the developer-keys item): add the old layout as
|
|
1689
|
+
your own refs. These are the exact former defaults; `<PREFIX>` is your
|
|
1690
|
+
env prefix (`ACME` for `acme`, `DEN` for Den's own checkout).
|
|
1691
|
+
|
|
1692
|
+
```yaml
|
|
1693
|
+
secrets:
|
|
1694
|
+
provider: 1password
|
|
1695
|
+
onepassword:
|
|
1696
|
+
account: example.1password.com # your account; Den injects none now
|
|
1697
|
+
refs:
|
|
1698
|
+
sudo:
|
|
1699
|
+
{ vault: Employee, item: Developer API Keys, field: <PREFIX>_LOCAL_ADMIN_PWD }
|
|
1700
|
+
journeys_agent_key:
|
|
1701
|
+
{ vault: Employee, item: Developer API Keys, field: CURSOR_API_KEY }
|
|
1702
|
+
developer_keys: { vault: Employee, item: Developer API Keys }
|
|
1703
|
+
```
|
|
1704
|
+
- **Relied on doppler or env for the saved sudo password** (the flat
|
|
1705
|
+
`<PREFIX>_LOCAL_ADMIN_PWD` key):
|
|
1706
|
+
|
|
1707
|
+
```yaml
|
|
1708
|
+
secrets:
|
|
1709
|
+
provider: doppler # or env
|
|
1710
|
+
refs:
|
|
1711
|
+
sudo: { key: <PREFIX>_LOCAL_ADMIN_PWD }
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
- **Relied on the implicit doppler default**: it still works. To silence
|
|
1715
|
+
the deprecation warning, say so: `secrets: { provider: doppler }`. A
|
|
1716
|
+
distribution that uses no secrets store can set `secrets: { provider:
|
|
1717
|
+
none }`.
|
|
1718
|
+
- A page or script that read `opAvailable` from
|
|
1719
|
+
`/api/sudo-password/vault-status` reads `backendAvailable`.
|
|
1720
|
+
- A `make auth-configure` or `make <cli>-setup-1password` in your own
|
|
1721
|
+
scripts needs `secrets.provider: 1password`.
|
|
1722
|
+
- None. Optional: set `brand.theme: <id>` in the org config to give the
|
|
1723
|
+
distribution's web UI its own default theme.
|
|
1724
|
+
- A `plugins/<id>/` with no `den-plugin.json` or `den-theme.json` now fails
|
|
1725
|
+
`plugin list` and `plugin validate`: give it a manifest or move it out of
|
|
1726
|
+
`plugins/`. A theme added from a `file://` path (0.3.0-dev.2 warned) stays
|
|
1727
|
+
listed, but re-add it from its published URL before committing the lock.
|
|
1728
|
+
- 0.3.0-dev.20: the header's health row (`#health`) and Local Dev's
|
|
1729
|
+
`#sudo-banner` are gone, and so are `planRow`, `fitPlan`, `moreChip`,
|
|
1730
|
+
`mountMoreChip`, `openMoreList` and `closeMoreList` in
|
|
1731
|
+
`public/health-fit.js`. A distribution page that read them reads the
|
|
1732
|
+
`<cli>:health-update` event (unchanged) or the status chip (`#den-status`)
|
|
1733
|
+
instead, and unlocks with `requireUnlock()` from `/header.js`.
|
|
1734
|
+
- 0.3.0-dev.8: a `den-theme.json` used as a plugin needs an `author`
|
|
1735
|
+
(`theme new` sets one). A plugin's `den` range is now enforced: write
|
|
1736
|
+
`>=0.3.0-0 <0.4.0` to admit Den's 0.3.0 prereleases, because `>=0.3.0`
|
|
1737
|
+
excludes them.
|
|
7
1738
|
## [0.2.8] - 2026-11-05
|
|
8
1739
|
|
|
9
1740
|
### Added
|