# Lockdown Behavior
This page is the exact behavior reference for what lockdown gates, and
what continues to work.
## Chrome and Menus
- The Project and Workspace taskbar buttons never render while
locked, and their click handlers and menu executors refuse even if a
stale click arrives.
- Workspace name tabs are hidden unless `:chrome :full`; clicking them
still refuses because switching is pinned.
- Minimised-restore badges are hidden at `:chrome :none` and `:chrome :app`.
- Panel title-bar close ✕ and minimise ▾ buttons disappear, as
does the group collapse button. Their hit-tests return nothing.
- The right-click group border menu does not open.
Focus movement, panel interaction, app menus, dialogs and tooltips are
unaffected.
## Single-App Presentation (`:chrome :app`)
`:chrome :app` is the most aggressive chrome level: it removes the
workspace entirely and leaves one app below its own title/menu strip.
| Element | `:chrome :none` | `:chrome :app` |
|---|---|---|
| Project/Workspace buttons | hidden | hidden |
| Workspace name tabs | hidden | hidden |
| Minimised-restore badges | hidden | hidden |
| Bottom taskbar row | reserved (empty) | not reserved |
| Group title bars / borders | drawn | none (one panel) |
| Panel-name palette | opens on app-name click | disabled (hotspot inert) |
| App title/menu strip | drawn | drawn (close/minimise buttons hidden) |
| Docks / pop-out | off | off |
### Launch and layout
- Named workspace configs are ignored. The app comes from the CLI
`retra/retro app <ref>`, then the policy `:app`; the launcher is never
a fallback.
- The policy `:app` source is exempt from the `:apps` allowlist by path
(like the vendor launcher). After the initial launch, `[tui/launch]`
and `[tui/adopt]` cannot displace the running app.
- `:layout true` does not re-enable layout mutation under
`:chrome :app`; the single panel is the session.
### Lifecycle
- `[tui/close]` and `[surface/quit]` on the sole app request a host
quit; the host also exits when the app's thread dies.
- A failed initial launch (bad source, refused by `:images-only`, …)
leaves the host up with the error in the log / error console — it
does not fall back to the launcher and does not exit.
- Ctrl+Shift+Q (both hosts), Ctrl+Q (`retra`) and the window X
(`retro`) quit as usual.
App menus, dialogs, focus, pub/sub and every runtime feature listed
under What Still Runs below behave exactly as in a normal locked
session.
## Fixed Layout
Every structural mutation is refused:
| Action | Mouse | Keyboard / prefix | Stdlib |
|---|---|---|---|
| Resize split borders | drag refused | `+`/`-` prefix | `[tui/resize]` |
| Split panel | — | `v` / `h` prefix, pie | `[tui/split]` |
| Close panel | ✕ button hidden | `x` prefix, pie | `[tui/close]` |
| Drag / reorder panels | title drag refused | — | — |
| Zoom | — | `z` prefix, pie | `[tui/zoom]` |
| Maximise group | — | `↑` prefix, pie | — |
| Collapse / minimise | button hidden | double-click refused | — |
| Add to group | border menu hidden | — | `[tui/add-to-group]` |
| Pop-out | palette item dimmed | `p` prefix | `[tui/pop-out]` |
| Dock regions | — | `d` prefix | `[tui/dock/*]` |
`popout_enabled` and `docks_enabled` stay off in lockdown, so the
pop-out/dock subsystems are inert. The vendor's own workspace spec is
still applied at startup — lockdown pins the layout the vendor
declared, it does not prevent the initial spec from building splits and
groups. (`:chrome :app` is the exception: it ignores workspace specs
and pins the one panel.)
## Launcher and Apps
- The launcher source is exactly the policy `:launcher` path; the
stdext fallback chain is suppressed.
- `[tui/launch]`, `[tui/adopt]` and the app selector all consult the
`:apps` allowlist before starting anything. The vendor launcher and
the policy `:app` source are exempt (matched by source path) so they
can always start; everything else goes through the allowlist.
- If the workspace config opened no apps, the policy `:app` runs first,
then the vendor launcher. Under `:chrome :app` the launcher is
suppressed entirely — the session needs a CLI `app <ref>` or `:app`,
and a failed launch leaves the host up with the error visible.
- With `:images-only true`, loading a source `.dft` app fails cleanly
in the shared app loader; only `.dftc` AOT images are accepted.
Note that the launcher UI itself is a Deft app. Lockdown enforces
the allowlist at the host launch verb, not in the launcher's own list
rendering — a badly written vendor launcher can show an app the host
will then refuse to start. (Under `:chrome :app` there is no launcher
UI at all.)
## Project and Workspace Pinning
- Project switching (Project menu, directory dialog, restart-in-dir)
refuses.
- Workspace switching (tabs, workspace switcher) refuses.
- New / Save / Save As / Remove Workspace refuse, including the Save As
dialog result path and the remove confirm.
- The host does not record the launch directory in the recent-projects
list while locked — a pinned device writes no user config.
## What Still Runs
Lockdown controls the interface, not the language runtime. Inside a
locked host:
- Apps run normally: drawing, input, widgets, dialogs, pub/sub, RPC,
config, workers, HTTP, and so on.
- `[tui/focus-id]`, focus chords and focus-by-index still move panel
focus.
- Theme cycling (`Ctrl+B t`) and app-level behavior are unaffected.
- The debug server, `eval`, `reload`, the filesystem, and the shell
remain available to app code.
## Security Posture
UI gating is a commercial/UX control, not a security boundary on
its own. An attacker with a shell can start the host without
`--lockdown`, call the stdlib directly, or attach to a runtime. Making
lockdown tamper-resistant is a layered job:
- Runtime lockdown (planned): disable `eval`/`reload`, source
loading, package installs, config writes and shell execution inside a
locked runtime.
- AOT images + `:images-only` (available): ship code as `.dftc` so
there is no source to edit on the device.
- Signed images and licenses (planned): verify origin before load.
- OS confinement (available): the Landlock sandbox
(`DEFT_SANDBOX=1`), a read-only root filesystem, a dedicated user,
and no shell in the boot path.
See Deployment for how to combine these.