Skip to content

Shell: the Organization rail — Environment picker with the gear, clean Apps tiles with the ⋯ menu, favourites, Marketplace and + Nový modul for Owners (F36 addendum, DEV-6639) - #154

Merged
immakermatty merged 13 commits into
mainfrom
claude/DEV-6639-shell-org-rail
Oct 4, 2026
Merged

immakermatty merged 13 commits into
mainfrom
claude/DEV-6639-shell-org-rail

Conversation

@immakermatty

@immakermatty immakermatty commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

Matěj and Anička tuned the shell wireframe on the night of 2026-10-03/04 (HumanAndMachine-ai/prototypes-lazurio 1cbad15, #4). Matěj asked for the Launchpad to follow its default navigation, the Organization rail, "cleanly, with no leftovers of the original wireframe concepts".

The reasoning behind the new model:

  • Generic Environment icons in folders look the same in every Organization, so a wrong click was easy.
  • An Organization's GitHub avatar is recognised at a glance.
  • So the rail switches Organizations, and the head of the left column picks the Environment.

The release goes to the hosted Environments tonight, and the people's PDF guide is re-shot from it.

This builds on #145 (Workspace / Productionspace, merged) and replaces the parts of #142 (F36) that the new model supersedes. Root decision 0179 (the rail of Environments) is being amended by the Operator to match.

What changes for people

  • Rail = Organizations. From the top:

    • the Lazurio logo (personal Dashboard) and the jump to any Environment (⌘⇧E);
    • your personal space, marked with your initials;
    • one GitHub avatar per Organization, the active one ringed in ink;
    • "+", which adds an Organization in the Dashboard;
    • at the bottom only the account, with your GitHub photo where known.

    Labels still show at once. A click on a space goes to the last Environment you were in there, else its first, in the same app. There are no Environment icons, folders, "+N", colour fills or gear in the rail any more.

  • Column head. The Environment picker shows a glyph, the Environment's name and who it is for. It never shows the machine's name: a Team Environment is "Team ", an Automated one its persona, a work one "Pracovní", a workstation "Tento počítač".

  • Apps column:

    • "Všechny moduly", "Soubory", Oblíbené (with a hint while there are none), and at the foot Marketplace marked "již brzy" (a placeholder page).
    • No search and no module list.
    • In Settings the column is their navigation with "Zpět".
  • Apps home:

    • The Organization's name with the Environment under it, and "Guide" (documentation.lazurio.ai) top right.
    • Workspace and Productionspace ("repozitáře"), with no subtitles.
    • Clean tiles: stone, name, short description, and a small star when favourite. No status row and no new-tab arrow.
    • The "⋯" menu at each tile's top right:
      • "Přidat do oblíbených" / "Odebrat z oblíbených";
      • "Informace o modulu", the existing module overview (in Productionspace "Informace o repozitáři", its GitHub page).
  • Tile click:

    • A module with an app opens it in a new tab as before (hosted on its own origin; locally start, then open).
    • Otherwise a short message in the wireframe's words: "Modul X zatím nemá aplikaci.", "Aplikace modulu X teď nejde spustit." or "Repozitář X nemá aplikaci."
  • Favourites sit in the column and come first in their section, in the column's order.

    • Until the Dashboard account holds them they live in this browser's localStorage, one list per Organization.
    • They are never in the Folder, which a Team Environment shares with its whole Team.
    • Only those the catalog has are shown.
  • "+ Nový modul" is the last Workspace tile.

Cleanly removed

  • From the rail: Environment icons and folders, "+N"/"less", the Organization accent fill and the gear.
  • From the Apps column: the search and the module list.
  • From the home: the head's Organization picker, the tile status rows and new-tab arrows, and the section subtitles.
  • Message keys that only those used, unused icon symbols, and their CSS.

The contract lazurio.shell.v1 changes in place, with no compatibility layer. Its producer and elements ship in one binary and no fork reads it yet:

  • an Environment's label is its own name or null, and it gains assignee;
  • an Organization drops accent and gains dashboard;
  • the operator gains avatar;
  • the document gains addOrganization.

Deliberately not changed / not in this PR

Part of #147 and #148.

Docs

  • Decision F36 addendum 2026-10-04: the new model, what it supersedes, the contract change, favourites, the Owner rule, and the issues.
  • "The shell as built" in docs/launchpad-development.md.
  • The vendor pin moves to design-system 5bbc1f78. Tokens, logo and stone bytes are unchanged, and the hashes still match.

Verification

  • CI=true bun run check: see the latest comment. One full run had two unrelated load-flaky tests, update-compiled and module-default-preparation, which pass alone; the rerun is green.
  • Tests:
    • tests/shell.test.ts: contract and parser, the producer's names (Team, persona, kind, assignee) and Dashboards, the rail's spaces and their targets per app, and the picker's list (one space, all, filtered).
    • tests/apps-view.test.ts: sections without subtitles, clean tiles, actions with the wireframe's messages, favourites first and the stored list read defensively, and the new-module prompt.
    • tests/organization-owner.test.ts: only an active admin counts; cached; a member, a refusal or a timeout is no; no call on a Team Environment.
    • tests/launchpad-shell-routes.test.ts: admission of the Owner route.
    • Plus route, markup and wording tests.
  • Chrome against temporary fixture Folders (bun scripts/preview-launchpad.ts hosted|local cs|en <port>; the preview's gh answers the Owner question for the example Organization). Clicked through:
    • the picker and its Organization head;
    • the gear into Settings and back to Apps;
    • favourite add, then reload (it persists), then remove;
    • the three messages;
    • "Informace o modulu" to the overview;
    • Marketplace;
    • "+ Nový modul": clipboard holds the prompt, a Chat tab opens, the message shows;
    • hosted tiles link to https://<module>.<vm>…/ in a new tab;
    • ⌘⇧E.
  • Side-by-side with the wireframe: Apps home, picker open, tile menu, a message, Settings, Files, Marketplace, the workstation home, and narrow 390. There are no rendered-page CI gates (root decision 0178).

Visual polish to the wireframe (round 2)

Measured against clean wireframe shots at 1cbad15, 1440×900:

  • Tiles. The stone is 44 px; the padding is 16/18 px, so descriptions fit as in the wireframe; the "+" box of "+ Nový modul" is 44 px too. On hover the border goes to ink with no shadow (design system lz-tile--row).
  • Column. Only the selected item is weight 600.
  • Productionspace. The folder icon is in ink.
  • Section rhythm. The pill keeps its normal line height (27 px). The head sits 7 px further from the first pill, the rule is 16 px from the tiles, and sections are 32 px apart.
  • Star and menu. The star stands about 14 px after the name. Menu rows are 33 px with a paper hover, and a favourite's menu shows the filled star. The "⋯" stays very quiet at rest (gray-300).
  • Width. The Apps page is capped at 1152 px, as the wireframe's page is.
  • Icons. The shell and the page switch from Lucide to Iconoir (MIT), the design system's interface set. The path data is exactly the wireframe's (app/v1/src/Icon.tsx, iconoir-react 7.12.1), drawn at stroke 1.5. The licence is vendored at src/shell/vendor/LICENSE-iconoir.txt with its hash in the README and the vendor test. The Lucide paths are gone.
  • Workstation. Named by its computer's host name without its domain (e.g. MacBook-Pro), second line "tento počítač", as the wireframe's workstation entry. A hosted Environment is still never named by its machine. Chat and Automate stay disabled there: a workstation has no recorded Chat or MausBot origin.
  • Organization without an avatar. Its initials. GitHub's generated pattern avatar comes from the same github.com/<login>.png when the login is bound; without a bound login there is nothing to ask without a network call.

For the forks (Lazurio/OpenMausBot#20), in the library:

  • (a) The host's tone. <lazurio-column-head> detects it from the nearest ancestor with an opaque background, by luminance, on connect and on a prefers-color-scheme change, and sets :host([data-host-tone=dark]): no frame, light text, a rgb(255 255 255 / .1) hover. A host may set --lazurio-host-tone: dark | light, which wins. The slot stays <lazurio-column-head active="automate">.
  • (b) The picker's list is a popover in the top layer (popover="auto", light dismiss), so a host's overflow or sticky bar cannot clip or cover it.
  • (c) Narrow host. Below 160 px of the element's own width (a container query) it shows the glyphs only: the picker's glyph, the gear and icon-only tabs.

Verified in Chrome: the dark host is detected and the custom property wins; the 80 px host shows glyphs only; the popover opens above a clipping, stacking column and closes on Escape.

Risks

  • Favourites are per browser until the Dashboard holds them, so they do not follow the person between browsers yet.
  • The Owner answer depends on the Environment's gh being signed in as the person. Without it the tile is simply absent.
  • Workstations: on a workstation that holds several Organizations, a click on an Organization in the rail opens Apps scoped to it (/o/<slug>). The other Environments in the rail come with the Dashboard.

🤖 Generated with Claude Code

Note

Rebuild shell rail as Organization spaces with Environment picker and Apps favourites (F36 addendum)

  • Replaces the rail's expandable Environment folders with a flat list of Personal and Organization spaces in view.ts, each linking to its remembered or first Environment, or its Organization Dashboard when empty. Remembers the last Environment per space in localStorage.
  • Adds a searchable, keyboard-driven Environment picker to the column head via the new Switcher component, next to a Settings gear and the app tabs. See elements.ts.
  • Reworks the Apps home into clean tiles with a ⋯ overflow menu for favourites and info links, per-Organization browser favourites, toasts, and an owner-gated + Nový modul tile that hands a prompt to Chat. See catalog-panel.ts.
  • Adds /api/organizations/<name>/owner and createOwnerCheck in server.ts and organization-owner.ts. Only an active admin GitHub membership grants the New module tile; all failures fail closed.
  • Adds the Marketplace route /marketplace, new English/Czech copy, shell icons, and design decision documentation in decisions.md.
  • Risk: Behavioral changes — the shell contract now requires assignee, dashboard URLs, addOrganization, and operator avatar fields; malformed shell documents are rejected in contract.ts. Organization accents are removed. Catalog search, refresh control, section subtitles, and the module/repository tree no longer render.

Macroscope summarized b718089.

immakermatty and others added 8 commits October 4, 2026 00:29
…n place (F36 addendum, DEV-6639)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tes and Marketplace, clean tiles with the ⋯ menu, toasts, + Nový modul behind GitHub's Owner answer

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, actions and favourites; GitHub's Owner answer, fail closed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… route

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… built; vendor pin 5bbc1f78 with unchanged bytes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@immakermatty

Copy link
Copy Markdown
Contributor Author

CI=true bun run check on head b718089aa4799855d7826c8918057378e615756e: green — lint, typecheck, 1223 pass / 0 fail across 137 files, check:public, build:proof, smoke:proof. (An earlier full run had two load-flaky tests unrelated to this change, update-compiled and module-default-preparation; both pass alone and in this rerun.)

@immakermatty
immakermatty marked this pull request as ready for review October 3, 2026 23:07

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review on exact head b718089 — CHANGES_REQUESTED. Two findings:

  1. [P1] Preserve canonical Organization slugs in the rail (src/launchpad/shell-document.ts:67-73). This filters organization.organization with the GitHub-login regex, although the canonical Organization manifest accepts a distinct slug (and src/shell/contract.ts:131 accepts _ and .). With a real synthetic Folder fixture, an executable Organization alpha_internal bound to GitHub alpha-forge appeared in the catalog with a module but was absent from shellOrganizations; neither the rail nor the Environment picker can reach it. Validate the slug according to its own supported contract, and validate only forgeLogin as a GitHub login. Add a regression using a canonical slug different from its bound login.

  2. [P2] Expire the browser owner answer (src/launchpad/catalog-panel.ts:493-515). The server cache expires after five minutes, but owners: Map<string, boolean | "reading"> retains each true or false for the entire page lifetime and never asks the server again, even after refresh() or a changed catalog. After the Environment loses Owner membership (or the bound login changes), "+ Nový modul" remains visible indefinitely until reload, contrary to the live/five-minute rule. Conversely a newly signed-in Owner never sees it. Revalidate on expiry/refresh and invalidate the answer when its login changes; this is a stale UI offer, not a server-side access grant.

Evidence: 32 focused tests passed; lint, typecheck, public check, proof build and smoke passed. Local owner HTTP probe: unadmitted 403, admitted active admin 200, POST 405. The server-side TTL returned true before expiry and false afterward; the browser has no corresponding expiry. CI is green on both platforms. No merge.

immakermatty and others added 3 commits October 4, 2026 01:41
…the page asks the Owner answer again after five minutes, a login change or a catalog read

The producer held Organization slugs to the GitHub login rule, so a canonical
slug with `_` or `.` (bound to a different login) dropped out of the rail and
the picker. The contract now admits the manifest's slugs (nonblank, at most
128, no control characters, no surrounding space); only `forgeLogin` is a
login. A real-Folder regression reads `alpha_internal.v2` bound to
`alpha-forge`.

The page kept each Owner answer for its whole life. `owner-answer.ts` now
holds the page's copy: it stands as long as the Launchpad's (one shared
constant), is asked again after it, per bound login, and after every catalog
read, keeping the last answer while it is asked again. Unit tests cover the
expiry, the login change and the reset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, section rhythm, 1152 px page, the workstation named by its computer; the picker's list in the top layer, the host's tone and a narrow host

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@immakermatty

Copy link
Copy Markdown
Contributor Author

Visual polish round on head 6a0d6547de90d6e09eca18af62367c6199ed79a7 (Iconoir icons, tile proportions and hover, section rhythm, 1152 px page, workstation name, and for the forks: host tone, top-layer picker list, narrow host). CI=true bun run check: green — lint, typecheck, 1227 pass / 0 fail across 138 files, check:public, build:proof, smoke:proof.

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review of exact head 6a0d654: CHANGES_REQUESTED. The canonical-slug fix is correct and the Folder regression passes. The owner-answer cache now keys by login and expires when read, but the page still does not uphold the stated five-minute live answer.

  1. [P2] Revalidate the visible Owner tile when its answer expires (src/launchpad/catalog-panel.ts:497-519, 892-965; src/launchpad/owner-answer.ts:22-30). Expiry is checked only from ownerOf() during a render; neither the owner answer nor the Apps page schedules a render at the deadline. Leave the Apps home open after an active admin answer and revoke that membership: at five minutes no request fires and "+ Nový modul" remains indefinitely until an unrelated route/render/refresh. The unit test calls read() manually after advancing its fake clock, so it misses this real-page behavior. Arrange a bounded expiry wakeup/revalidation (and cleanup if needed), then prove the rendered tile updates without navigation or reload. This remains only a UI offer, not a server authorization bypass.

  2. [P2] Ignore obsolete Owner responses after a catalog refresh (src/launchpad/owner-answer.ts:32-43, src/launchpad/catalog-panel.ts:503-517, 969-983). clear() erases the map but does not invalidate outstanding GETs; refresh() immediately renders the old catalog, starting a new GET before the new catalog arrives. If an older GET A returns after the refreshed GET B, its settle() overwrites B for the same Organization/login and can restore a stale true for another five minutes. Reproduced on this exact source: start A, clear, start B, settle B=false, settle A=true => read() returns {owner:true,ask:false}. Guard settlements with a request generation/identity and cover this ordering in a regression.

  3. [P3, documentation] docs/decisions.md:3609-3615 still says a workstation is "Tento počítač" and is never named by a machine, whereas shell-document.ts:159-165, 196-200 now labels it with the computer hostname. If that polish is intentional, update the F36 addendum's naming contract so the decision and implementation agree.

Evidence: isolated checkout at the SHA, git diff --check clean, 31 focused tests pass (slug, shell, Owner, Apps, routes); Ubuntu/macOS CI green, 0 review threads and no other pending reviewer. An attempted broader local gate exceeded the command wait and produced no usable result; not claimed as passed. @immakermatty please address these findings together on a new head and request re-review. No merge.

… answer to the question it is asking; the workstation's name in F36

Pablo's round 2: the page checked an Owner answer's expiry only while it
rendered, so an open Apps home kept "+ Nový modul" past the five minutes; and
a late answer to a question asked before a catalog read could overwrite the
newer one. `owner-answer.ts` now numbers each question and settles only the
one being asked (a catalog read or a newer question drops the older), and
names the earliest expiry; the page sets one timer for it and renders then,
which asks again. Unit tests cover the stale and overtaken answers and the
expiry. The F36 addendum names a workstation by its computer, as built.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review of exact head 1fd6c2f: CHANGES_REQUESTED.

The numbered-question settlement fixes the obsolete-response race; the F36 workstation naming now matches the implementation. The expiry timer fixes the single-Organization case, but a previously visited Organization can consume its one scheduled wakeup.

[P2] Re-arm the Owner-expiry timer after an expiry wake when the expired answer is not the visible Organization (src/launchpad/catalog-panel.ts:525-533, 627-647). Reproduction: visit Organization A; its Owner answer settles at t=1 s (expires t=301 s). Switch to B; its answer settles at t=2 s (expires t=302 s). wakeForOwners() schedules the only timer at t=301 s. At that time render() reads only B; B is still valid and starts no GET, so no .finally() re-arms the timer. B stays open after t=302 s with its old "+ Nový modul" tile and no recheck until unrelated interaction. Source-level probe of the actual createOwnerAnswers() at t=301 s returned B {owner:true,ask:null} and nextExpiry()=301000, although B expires at 302000. The timer callback only invokes render(). Prune inactive answers or advance/re-arm the timer across expired off-screen answers without a one-second spin, and cover A→B navigation plus B expiry in a regression.

Evidence: isolated checkout pinned to 1fd6c2f; git diff --check clean; 27 focused tests, lint and typecheck pass. Both GitHub CI jobs pass. A broader local run timed out waiting/executing and is not claimed as passed. Zero unresolved threads and no competing reviewer request at the pre-write gate. @immakermatty please address this remaining P2 on a new head; no merge.

…an expired answer off screen sets no wake

Pablo's round 3: visiting Organization A then B left one timer at A's
expiry; at that wake only B rendered, B still stood, nothing re-armed, and B's
expiry was missed. `nextExpiry()` now names the earliest answer that still
stands, and the wake re-arms after rendering, so the page wakes for B next;
an expired answer of an Organization not shown is asked again only when shown
and keeps no timer spinning. A regression walks A → B → both expiries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review of exact head 843ae05: APPROVED.

The remaining P2 is fixed: nextExpiry() skips expired off-screen answers; the expiry callback renders the visible Organization and re-arms for the next standing answer. In the A (t=1 s) → B (t=2 s) regression, A's wake at t=301 s leaves B valid and schedules t=302 s; B's wake starts its new question without an off-screen timer loop. The prior obsolete-response guard and F36 workstation naming remain unchanged in this repair delta.

Evidence: isolated clean checkout at this SHA; 38 focused tests across Owner, Apps, shell and routes passed; git diff --check clean. Exact-head GitHub CI passed on Ubuntu 24.04 and macOS 14 (full lint, typecheck, test, public check, proof build and smoke). Zero unresolved review threads and no competing requested reviewer at the pre-write gate. A separate local full-check attempt exceeded the tool wait, so I do not claim it passed locally; CI supplied the full-gate result. @immakermatty exact-head QA complete. No merge performed.

@immakermatty
immakermatty merged commit 8fb019d into main Oct 4, 2026
3 checks passed
immakermatty added a commit that referenced this pull request Oct 4, 2026
…ctual identity

Pablo's review: the target said Team and Automated Environments act under
the Team's identity. A Team Environment acts through the brokered Lazurio
for GitHub app within its Team's grants (root 0147, 0165); an Automated
Environment through its persona's own GitHub account (root 0169). The tile
follows GitHub's live answer for the actual identity and fails closed,
which is the Owner check #154 built (F36 Organization-rail addendum, 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai pushed a commit that referenced this pull request Oct 4, 2026
…nt picker with the gear, quiet selection, clean Apps column and tiles (DEV-6639) (#150)

* Docs: the Lazurio shell's second iteration — rail of spaces, Environment picker with the gear, quiet selection, clean Apps column and tiles (DEV-6639)

Records the target decided on 2026-10-04 (shell wireframe review, confirmed
by Matěj) in the target shell of launchpad-development.md and as an
addendum to decision F36: the rail switches spaces (personal space and one
GitHub avatar per Organization, account photo at the bottom, ink ring),
the column head carries the Environment picker with the Settings gear,
Environment names say what the Environment is for, selection is the
design system's quiet surface, and Apps lands in the last Environment with
a column of Všechny moduly, Soubory, Oblíbené and Marketplace, clean tiles
with a "⋯" menu, favourites and "+ Nový modul". Passages rewritten by
#145 (the two sections) are left to it. Workspace composition 7.5, the
parity's visual-design note and the marketplace status follow. Nothing is
implemented; issues #146, #147 and #148 track it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: cite root decision 0185 for the shell's second iteration

The target shell and the F36 addendum named root decision 0179 as what the
second iteration supersedes, but not the root record of the iteration
itself: decision 0185 (HumanAndMachines/Lazurio#487).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: F36 point 1 records the first build; the Organization-rail addendum supersedes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: + Nový modul follows the live capability of the Environment's actual identity

Pablo's review: the target said Team and Automated Environments act under
the Team's identity. A Team Environment acts through the brokered Lazurio
for GitHub app within its Team's grants (root 0147, 0165); an Automated
Environment through its persona's own GitHub account (root 0169). The tile
follows GitHub's live answer for the actual identity and fails closed,
which is the Owner check #154 built (F36 Organization-rail addendum, 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants