Skip to content

Spell the branch's slashes in the name a tab reads - #626

Merged
blooop merged 4 commits into
mainfrom
herdr_title
Sep 17, 2026
Merged

blooop merged 4 commits into
mainfrom
herdr_title

Conversation

@blooop

@blooop blooop commented Sep 17, 2026

Copy link
Copy Markdown
Owner

dl blooop/devlaunch@feature/auth titled the terminal, the herdr tab and the
container's prompt devlaunch@feature-auth. The flattening is the workspace
id's to do, because an id is a devpod workspace name and a DNS label and can
hold no slash. A tab is neither, and it was paying a cost that was never its
own. What it cost was the one thing a tab is for: feature/auth and the
different branch feature-auth are two workspaces with two ids, and both tabs
said the same thing.

WorkspaceId::label is the one derivation behind all four emitters (the OSC 2
title, herdr tab rename, the container PS1 line, and the tab name dl writes
before a launch resolves), so the change is one place. fit_ref now takes the
separator to write between the segments that survive its cut, and parts_of
calls it twice: once with - for the id, once with / for the label. Both
separators are one character, so the budget, the middle segments dropped and the
character the cut falls on are identical either way, which is what keeps a tab
matchable by eye against a dl --ls row.

The slash lands where the branch had one and nowhere else, because it comes from
the segment split rather than a pass over the finished id: a dash the slug made
inside a segment stays a dash, so dependabot/github_actions/x labels as
dependabot/github-actions/x. The golden id vectors are untouched, and no
workspace id, clone directory, hostname or devpod name moves.

Reviewed in fresh context on three axes before the push. Defects and Types found
no correctness defect (the two-spellings invariant was brute-forced over ~1.6M
ref and budget combinations); the three commits after the first are what the
review found: a test comment that credited validate_ref_name with a guard it
does not have, a label() docstring example whose output that repo cannot
produce, and a README sentence the change had falsified.

🤖 Generated with Claude Code

Summary by Sourcery

Preserve branch separators in workspace labels so tabs and prompts distinguish branches without changing workspace IDs.

New Features:

  • Preserve branch slashes in workspace labels used for terminal, herdr, container prompt, and early launch tab names while retaining flattened workspace IDs.

Bug Fixes:

  • Distinguish tab names for branches such as feature/auth and feature-auth that previously rendered identically.

Enhancements:

  • Keep workspace labels aligned with ID truncation and segment selection while restoring the branch separators without altering IDs, hostnames, clone directories, or devpod names.

Documentation:

  • Update the changelog, README, and workspace documentation to describe slash-preserving tab labels and their relationship to workspace IDs.

Tests:

  • Expand workspace label and launch naming coverage for slashed refs, separator preservation, truncation consistency, and safe prompt/title rendering.

`dl blooop/devlaunch@feature/auth` titled the terminal, the herdr tab and the
container's prompt `devlaunch@feature-auth`. The flattening is the workspace
*id*'s to do: an id is a devpod workspace name and a DNS label and can hold no
slash. A tab is neither, and it was paying a cost that was never its own. What
it cost was the one thing a tab is for: `feature/auth` and the different branch
`feature-auth` are two workspaces with two ids, and both tabs said the same
thing.

`WorkspaceId::label` is the one derivation behind all three emitters, so the
change is one place. `fit_ref` now takes the separator to write between the
segments that survive its cut, and `parts_of` calls it twice -- once with `-`
for the id, once with `/` for the label. Both separators are one character, so
the budget, the middle segments dropped and the character the cut falls on are
identical either way, which is what keeps a tab matchable by eye against a
`dl --ls` row.

The slash lands where the branch had one and nowhere else, because it comes from
the segment split rather than from a pass over the finished id: a dash the slug
made inside a segment stays a dash, so `dependabot/github_actions/x` labels as
`dependabot/github-actions/x`. The golden id vectors are untouched and no
workspace id, clone directory, hostname or devpod name moves.

Claude-Session: https://claude.ai/code/session_015TVcYe8qh2n6Z5FxEyox4Z
`is_safe_name` constrains only the first character, so `feature/auth/`
parses fine and reaches `label`. The comment claimed `validate_ref_name`
refused it, and that claim was the only reason the case was missing from
the assertion list.

The label assertions pass on it unchanged -- the empty-segment filter was
already doing the work the comment credited to the parse boundary.

Claude-Session: https://claude.ai/code/session_015TVcYe8qh2n6Z5FxEyox4Z
The paragraph on where a slash lands published `dependabot/github_actions/codecov`
labelling as `dependabot/github-actions/codecov`. With `devlaunch` -- the repo
every other example in the same doc comment uses -- the ref has 32 characters of
room and joins to 33, so `fit_ref` drops the middle segment and the real label is
`devlaunch@dependabot/codecov`. The output named there needs a repo slug of eight
characters or fewer, which the comment never said.

The claim itself is true and stays: a dash `slug` made inside a segment stays a
dash. Only the example moves, to the one the table in
`a_label_is_the_id_with_the_suffix_off_and_its_separators_respelled` already
pins, so the prose and its guard cannot drift apart.

Claude-Session: https://claude.ai/code/session_015TVcYe8qh2n6Z5FxEyox4Z
The one place the change missed. `docs/workspaces.md`, `docs/workspace-tools.md`
and the changelog all moved with the code; this line went on describing the tab
as the id with "the dash before the branch spelled `@`", which is now half of
what a label does. Nothing guards it -- `test_readme_cli_doc.py` reads the README
for flags and the version, not for this prose -- so it drifted silently.

Claude-Session: https://claude.ai/code/session_015TVcYe8qh2n6Z5FxEyox4Z
@sourcery-ai

sourcery-ai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Workspace labels now preserve branch slashes using a parallel rendering derived from the same truncation and segment selection as the unchanged dashed workspace ID. Tests cover matching, truncation, slug-vs-separator behavior, shell safety, and all tab/title emission paths, while documentation and the changelog explain the distinction.

Flow diagram for workspace ID and label rendering

flowchart LR
    Spec["owner/repo@ref"] --> Parts["parts_of"]
    Parts --> FitId["fit_ref(..., ID_SEPARATOR)"]
    Parts --> FitLabel["fit_ref(..., REF_SEPARATOR)"]
    FitId --> ID["WorkspaceId::value\ndashes for separators"]
    FitLabel --> Label["WorkspaceId::label\nslashes for ref separators"]
    ID --> Addressed["devpod / hostname / dl --ls"]
    Label --> Tabs["terminal title / herdr tab / PS1"]
Loading

File-Level Changes

Change Details Files
Preserve ref separators in workspace labels while keeping IDs unchanged.
  • Parameterize ref fitting by separator and derive dashed ID and slash-preserving label from the same segment selection and length budget.
  • Store the readable ref spelling in Parts and use it from WorkspaceId::label.
  • Keep slug-generated dashes distinct from separators, including for nested refs and empty segments.
rust/devlaunch-core/src/domain/workspace_id.rs
Expand coverage for label rendering and downstream tab/title behavior.
  • Update workspace ID tests for slash preservation, branch disambiguation, truncation parity, and safe rendering.
  • Update launch, provisioning, and early tab-name tests to expect slash-preserving labels while asserting IDs and shell safety remain unchanged.
rust/devlaunch-core/src/domain/workspace_id.rs
rust/devlaunch-core/src/flows/launch.rs
rust/devlaunch-core/src/flows/provision.rs
rust/dl/src/lib.rs
Align user-facing documentation and release notes with the new tab spelling.
  • Document labels as slash-preserving renderings of the same cut as workspace IDs.
  • Update examples and rendering tables to distinguish feature/auth from feature-auth without changing IDs or hostnames.
  • Add the behavior to the unreleased changelog.
README.md
docs/workspace-tools.md
docs/workspaces.md
CHANGELOG.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="rust/devlaunch-core/src/domain/workspace_id.rs" line_range="533" />
<code_context>
 struct Parts {
     repo: String,
     git_ref: String,
+    git_ref_read: String,
     suffix: String,
 }
</code_context>
<issue_to_address>
**nitpick:** The documentation immediately above `Parts` says that any of the three pieces can be empty, but `Parts` now carries four pieces after adding `git_ref_read`. That leaves the struct contract inaccurate and obscures that the readable ref copy has the same empty-state behavior as the id copy.

**Suggested fix:** Update the comment to describe the four fields, or explicitly distinguish the three id components from the additional readable-ref component.

```suggestion
/// Any of the three id components can be the empty string, which [`join`] drops rather than
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@@ -496,9 +532,17 @@ fn join_parts(parts: &Parts) -> String {
///
/// Any of the three can be the empty string, which [`join`] drops rather than

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: The documentation immediately above Parts says that any of the three pieces can be empty, but Parts now carries four pieces after adding git_ref_read. That leaves the struct contract inaccurate and obscures that the readable ref copy has the same empty-state behavior as the id copy.

Suggested fix: Update the comment to describe the four fields, or explicitly distinguish the three id components from the additional readable-ref component.

Suggested change
/// Any of the three can be the empty string, which [`join`] drops rather than
/// Any of the three id components can be the empty string, which [`join`] drops rather than

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.99%. Comparing base (f88998f) to head (d9831de).

Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.24% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 95.24% <100.00%> (+<0.01%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant