Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,12 @@ anything to its `devcontainer.json`.
`DEVLAUNCH_ZELLIJ=1` is the ask, once in a shell profile or per launch; it costs 2.2s to 3.5s
of a cold launch, which is why it waits to be asked.
- **A terminal named after the workspace.** `dl blooop/devlaunch` names the pane
`devlaunch-main-3j1t` in zellij, tmux, or a plain terminal window: the workspace id,
the same string `dl --ls` prints and the container's hostname carries.
`devlaunch@main` in zellij, tmux, or a plain terminal window: the workspace id read for
a person, with the hashed suffix off and the dash before the branch spelled `@`, where
`dl --ls` and the container's hostname both say `devlaunch-main-3j1t`. A workspace you
name by its id keeps the id.
<!-- Also stated in docs/workspace-tools.md and rust/.../flows/launch.rs;
test_title_claim_agrees.py holds the three to one answer. -->
- **A shared pixi package cache**, bound in from the host, so dotfiles that provision tools with
`pixi global sync` download each package once per machine instead of once per container. On one
measured profile that is 18s to 28s instead of 62s to 113s and 1.2 GB.
Expand Down
38 changes: 38 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,44 @@ reporting an unknown workspace called `prune`, and a workspace that really is ca
`prune` is still reachable as `dl stop prune`. Use `dl <ws> rm` from now on.


### Where `--force` may sit

`--force` is read in one position only: after both the workspace and the verb, which
is where every example on this page writes it. The two slots ahead of it are already
spoken for, so a `--force` that lands in either is read as the word that belongs
there and refused:

```
dl <ws> --force rm -> Unknown command '--force'. (exit 1, nothing deleted)
dl --force <ws> rm -> Unknown workspace '--force'. (exit 1, nothing deleted)
```

**Past the verb it is read, but only `rm` and `rme` do anything with it.** Every
other verb takes the flag and drops it: `dl <ws> up --force`, `dl <ws> stop --force`
and `dl <ws> recreate --force` all run exactly as they would without it, silently.
That is the one place this grammar discards a word rather than refusing it, and it is
worth knowing because the two halves of the rule read as though they were one: a
misplaced `--force` refuses, a meaningless one does not. `kill` is the case to
remember, since it is the verb whose whole point is going ahead anyway. It has no
`--force` to type and ignores one offered.

**The selector form cannot be forced at all.** `dl rm` with no workspace opens the
picker, which leaves no slot after the verb, so `dl rm --force` is the verb-slot
refusal above. There is no spelling of "pick some workspaces and force the removal";
name the workspace, or answer the refusal the guard prints. The diagnostic's
suggestion (`dl rm -- --force`) is the generic one for an unknown verb word and would
run `--force` as a shell command, which is not what anybody typing that meant.

**A global command has no slots, so placement stops mattering there.**
`dl --force --prune` and `dl --prune --force` are one line, and the same holds for
every other global. Note this is about the literal word `--force`: `--devcontainer`,
`--force-worktrees` and `-y` have rules of their own.

Pinned by `force_deletes_only_where_it_follows_both_the_name_and_the_verb` and
`a_globals_force_reads_the_same_wherever_it_sits` in `rust/dl/src/cli.rs`, and by
`force_after_the_verb_still_deletes` with its two neighbours in
`rust/dl/tests/grammar.rs`.

## Remote Control: every `aid` session, on your phone too

Every `aid` launch of claude starts with Claude Code's Remote Control on. There is no
Expand Down
3 changes: 2 additions & 1 deletion docs/workspace-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,8 @@ session; this one writes an escape sequence and two lines into a profile.
`<repo>@<branch>`, with the hashed suffix off.** `dl blooop/devlaunch` names the pane
`devlaunch@main` where devpod, the container's hostname and the `WORKSPACE` column of
`dl --ls` all say `devlaunch-main-3j1t`. One string with two characters changed, so a
tab and a listing row still match by eye.
tab and a listing row still match by eye. The same sentence is in `README.md` and in
`flows/launch.rs`, and `test_title_claim_agrees.py` holds all three to one answer.

Two characters, and they are the two a glance cannot use. The suffix carries the
workspace's identity and none of its meaning: it is what keeps two branches whose
Expand Down
6 changes: 6 additions & 0 deletions rust/devlaunch-core/src/flows/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,12 @@ pub(crate) fn dotfiles_update(
/// devpod, the hostname and the `WORKSPACE` column of `dl --ls` all say
/// `devlaunch-main-3j1t`.
///
/// That sentence is also written in `README.md` and `docs/workspace-tools.md`, and
/// `test_title_claim_agrees.py` diffs both of them against this one: the prose is
/// held against the module that computes the title rather than against itself,
/// because two pages can agree and both be wrong. Changing the name here means
/// changing it there in the same commit.
///
/// **It is the id, not a second derivation of the spec.** The slugs and the
/// truncation are [`WorkspaceId::label`]'s, which are [`WorkspaceId::value`]'s, so a
/// tab and a listing row still match by eye: one is the other with a suffix removed
Expand Down
85 changes: 85 additions & 0 deletions test/test_title_claim_agrees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""The terminal title is stated in three places, so the three statements are diffed.

The title is a fact that has to appear more than once: the README's feature list
is where a reader first meets it, `docs/workspace-tools.md` is where the
reasoning for the spelling lives, and `flows/launch.rs` is where it is computed
and where the doc comment explains the derivation to whoever changes it. That
makes two hand-maintained copies of what the third one does, which this
repository allows only with a test beside them that diffs the copies.

The rule earned the test rather than being applied on principle. The README and
the docs page had already drifted apart on this exact sentence, in opposite
directions, about the same example command, and nothing failed:
`test_readme_cli_doc.py` holds a flag to being mentioned, not a claim to being
true.

**`launch.rs` is in the list, and it is the reason this guard means anything.**
Diffing the two prose pages against each other would only pin that they agree,
which the wrong pair of edits satisfies as easily as the right one: had the
drift been repaired by editing the docs page down to the README's wrong string,
a two-page guard would have blessed it. The module that computes the title is
the one copy that cannot be wrong without the behaviour being wrong, so it is
the one the prose is held against.

The instrument is the sentence all three already write about one example
command, so no page carries a marker for this test's benefit. Anchoring on the
command is also what keeps the guard honest in both directions: `docs/` documents
the cases that are titled *differently* (a path spec, a bare id), and those
sentences name a different command, so documenting one more of them cannot fail
this test. It also cannot collide with "every prompt *renames* the pane", which
`docs/workspace-tools.md` and `flows/provision.rs` both say about the shell
prompt's own later write.
"""

from __future__ import annotations

import re
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent

# The three places the fact is written. `launch.rs` is not optional: see the
# module docstring for why a prose-only diff would pass the inverted repair.
SOURCES = (
Path("README.md"),
Path("docs") / "workspace-tools.md",
Path("rust") / "devlaunch-core" / "src" / "flows" / "launch.rs",
)

# The claim, anchored on the one example command all three use, across whatever
# line break each happens to wrap at. Anchored rather than matched loosely so
# that a page documenting a differently-titled example cannot fail this test.
CLAIM = re.compile(r"`dl blooop/devlaunch` names the pane\s+(?://[/!]?\s*)?`([^`]+)`")


def _claims(relative: Path) -> list[str]:
return CLAIM.findall((REPO_ROOT / relative).read_text(encoding="utf-8"))


def test_every_source_still_states_the_pane_name():
"""A claim that vanished would make the comparison below vacuously true."""
for relative in SOURCES:
assert _claims(relative), (
f"{relative} no longer says what `dl blooop/devlaunch` names the pane. "
"Either restore the claim or retire this guard along with the copy it "
"diffs"
)


def test_the_prose_agrees_with_the_module_that_computes_the_title():
truth = Path("rust") / "devlaunch-core" / "src" / "flows" / "launch.rs"
computed = set(_claims(truth))

assert len(computed) == 1, (
f"{truth} states more than one pane name for the same command: "
f"{sorted(computed)}. This guard reads it as the answer, so it has to be "
"one answer"
)

for relative in SOURCES:
assert set(_claims(relative)) == computed, (
f"{relative} says `dl blooop/devlaunch` names the pane "
f"{sorted(set(_claims(relative)))}, and {truth} says "
f"{sorted(computed)}. The module is where the title is computed, so "
"the prose is what moves unless `titled()` itself changed"
)
Loading