Skip to content
Merged
67 changes: 67 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3700,6 +3700,73 @@ application, not which Teams reach a module's repository), remembering the last
Environment after sign-in (the Dashboard's), and Chat opening with the prompt already in
its composer (Lazurio/t3code#35).

**Built behind the person's account, 2026-10-04 (root decision 0185 S8, S12, S15,
S18; F37's addendum of the same day; issues #148, #151).** "Přístup k modulu", the
report of the last Environment, favourites in the account and where module apps open
move from not built to built. Each waits only for the person's account to answer on
the Environment's own origin (`/.lazurio/account/…`, relayed by the gateway); until it
does, everything behaves as points 6 and 7 say.

- **The account document** (`src/launchpad/account.ts`). Apps reads `GET
/.lazurio/account/environments` once per page load: same origin, no token, no
redirect followed, given up after 4 s. Only `schema: "lazurio.account.v1"` counts,
and Apps takes two things of it: `preferences.openApps` (`same`; anything else is
`tab`) and `favourites`, keyed by the Organization slug. The rest is the library's
merge (F37 point 4).
- **Where apps open** (S18, `app-opening.ts`). Tiles, the column's favourites,
"Otevřít aplikaci" and the lifecycle's "Otevřít" open in a new tab (`tab`, also
without the account) or navigate this window to the app's own address (`same`),
never in a frame. On a workstation the new tab is opened within the click and led to
the app once it started; with `same` the window moves only after the start
succeeded. The new-tab arrow shows only with `tab`.
- **Favourites** (S12). With the account, an Organization's favourites are its list,
in its order, first in their section with the star as in point 7. The star writes
`PUT` or `DELETE /.lazurio/account/favourites/<org slug>/<module|repository>/<id>`
at once. One favourite has one write under way at a time, sent in the order of the
clicks, so the account ends as the last click wants; a click made while a write is
under way is sent after it, only if it still differs from what the account holds. A
failure is put back, with a short message, only when no later click wants something
else; the favourite then shows what the account holds, in its place. Different
favourites never wait for each other. Browser favourites are
never uploaded: `localStorage` stays only for an Environment without the account and
for the Personalspace group, which is no Organization.
- **The last Environment** (S8, `src/shell/last.ts`). The library sends `PUT
/.lazurio/account/last` with `{ "environment", "app", "organization" }` (this
document's `current`, which is the Environment's base host by the identity of an
Environment entry above, the app its host names, and the space's Organization slug
or null) once per full page load of Apps, Chat and Automate: `keepalive`, its answer
not read, silent on failure, never blocking drawing. It is its own module, apart from
the rail's merge of the account's Environments, and each element calls it with one
line.
- **"Přístup k modulu"** (S15, #151). A module tile's menu offers it to the
Organization's Owners (point 8's answer) and Stewards. A Steward's answer is GitHub's
`permissions.maintain` on the module's declared repository (the slot's `git.url`,
which the catalog now carries as the module's `url`): `gh api repos/<owner>/<repo>`,
read behind the same admission (`GET
/api/organizations/<org>/modules/<module>/maintain`), bounded by the Owner check's
timeout, kept five minutes per repository, one question at a time, and no on every
failure. It is never asked on a Team Environment or for a repository outside the
Organization's GitHub login. The page asks only when the person reaches for a
module's menu, never for every tile, and the item joins the open menu when the answer
comes. It opens `<the Organization's Dashboard page>/settings?tab=modules&module=<id>`
in the same window, without an arrow. A production repository and the Personalspace
group never have it, nor does an Organization without its Dashboard page. The
Dashboard decides again with its own live read; the item grants nothing.
- **The Dashboard's slug.** The Dashboard addresses an Organization by its canonical
slug, the manifest's slug lowercased with every run of other characters one `-`, and
matches `/orgs/<slug>` exactly. Point 4's `dashboard` used the slug as written, so an
Organization whose slug has capitals led to the Dashboard's not-found page; it now
uses the canonical form, and "Přístup k modulu" builds on it. The Dashboard
disambiguates two of a person's Organizations that reduce to one slug with a suffix
an Environment cannot know; such a link still ends on its not-found page.

Verified by unit and HTTP tests (the document read and refused, a timeout, the open
modes with a workstation's start, the favourites' writes and their rollback, the report
once per load and silent, the Steward's answer, its route and the catalog's `url`, the
item's target and its absence, the Dashboard's slug) and in Chrome against the hosted
preview as an Owner, a Steward and a member, and the local preview in both open modes,
with the account stood in by the browser automation.

Verified by unit and HTTP tests: the contract and its parser, the producer's names (Team,
persona, kind, assigned person) and Dashboards, the rail's spaces and their targets in
each app, the picker's list (one space, all of them, filtered), the Apps sections without
Expand Down
66 changes: 51 additions & 15 deletions docs/launchpad-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,12 @@ Environments, folders and a gear in the rail, the addendum supersedes it.
`view.ts` (the rail's spaces, the Environment picker's list, names and who lines, the
switch, as pure functions), `elements.ts` (`<lazurio-rail>`, `<lazurio-column-head>`),
`styles.ts` (the vendored tokens on `:host`, the design system's selection rule),
`stones.ts`, `fonts.ts`, `index.ts` (the entry of `/.lazurio/shell.js`) and `vendor/`
(tokens, logo, fonts and stones with their hashes). Tests: `tests/shell.test.ts`,
`tests/launchpad-shell-routes.test.ts`, `tests/apps-view.test.ts`,
`tests/organization-owner.test.ts`.
`stones.ts`, `fonts.ts`, `last.ts` (the report of the last Environment used,
below), `index.ts` (the entry of `/.lazurio/shell.js`) and `vendor/` (tokens, logo,
fonts and stones with their hashes). Tests: `tests/shell.test.ts`,
`tests/shell-last.test.ts`, `tests/launchpad-shell-routes.test.ts`,
`tests/apps-view.test.ts`, `tests/account.test.ts`, `tests/app-opening.test.ts`,
`tests/organization-owner.test.ts`, `tests/module-maintainer.test.ts`.
- **Identity of an Environment entry** ([F37's addendum of
2026-10-04](decisions.md#addendum-of-2026-10-04-account-writes-one-namespace-the-environment-known-from-the-token)).
The document's `current` and every Environment's `id` are the Environment's base host
Expand All @@ -342,27 +344,61 @@ Environments, folders and a gear in the rail, the addendum supersedes it.
space's first Environment. Tests: `tests/shell-environment-id.test.ts`.
- **Routes.** `/.lazurio/shell.js`, `/.lazurio/fonts/<file>` and
`/.lazurio/stones/<file>` beside the page's routes (`src/launchpad/page.ts`);
`/.lazurio/shell.json` and `/api/organizations/<org>/owner` in the server after
admission (`src/launchpad/server.ts`, producer `shell-document.ts`, the Owner
answer `organization-owner.ts`).
`/.lazurio/shell.json`, `/api/organizations/<org>/owner` and
`/api/organizations/<org>/modules/<module>/maintain` in the server after admission
(`src/launchpad/server.ts`, producer `shell-document.ts`, the Owner answer
`organization-owner.ts`, a Steward's answer `module-maintainer.ts`). The page and the
library also speak to `/.lazurio/account/…` on the same origin, which the
Environment's gateway relays to the person's account (F37 and its addendum); the
Launchpad does not answer it.
- **Page.** `index.html` is rail | column | main. The Apps column (all modules, Files,
favourites, Marketplace) and home are `catalog-panel.ts` over `apps-view.ts` and
`favorites.ts`, the frame is `shell.ts`, and the switch's pairing and the new
module's hand-over to Chat are in `ui.ts`. A module's name, line and stone come from
its default app's declaration (title, description, `icon`, tags), carried by the
catalog as display-only `display`; the stone falls back by the root Launchpad's
org-agnostic semantic key, and the Module's own manifest stays the authority.
Favourites live in the browser per Organization. Settings, Tools, Recovery and Files
keep their panels. The sections below that speak of the sidebar, its footer, the top
Settings, Tools, Recovery and Files keep their panels. The sections below that speak of the sidebar, its footer, the top
bar, T3 Code's colours or Chat and MausBot as sidebar links describe the build before
F36.
- **The person's account** ([F36's account
note](decisions.md#f36--the-lazurio-shell-one-library-in-the-platform-served-at-lazurioshelljs-with-lazurioshelljson-the-launchpad-is-its-first-consumer),
root decision 0185 S8, S12, S15, S18). Built, and waiting only for the account to
answer on the Environment's own origin; until the gateway relays
`/.lazurio/account/` (404 today) everything behaves as before it:
- `account.ts` reads `GET /.lazurio/account/environments` once per page load
(same origin, no token, given up after 4 s). Only a `lazurio.account.v1` document
counts, and Apps takes two things of it: `preferences.openApps` and `favourites`
keyed by the Organization slug.
- Where apps open (S18, `app-opening.ts`): tiles, the column's favourites, "Otevřít
aplikaci" and the lifecycle's "Otevřít" open in a new tab (`tab`, also without the
account) or navigate this window (`same`), never in a frame. On a workstation the
tab is opened within the click; this window moves only once the start succeeded.
- Favourites (S12): with the account, an Organization's favourites are its list, in
its order, and the star writes `PUT`/`DELETE
/.lazurio/account/favourites/<org slug>/<module|repository>/<id>` at once. One
favourite's writes go one at a time in the order of the clicks, and a failure is put
back with a short message only when no later click wants something else. Without
the account, and for the Personalspace group, they stay in the browser per
Organization (`favorites.ts`); browser favourites are never uploaded.
- The last Environment (S8, `src/shell/last.ts`): the library sends `PUT
/.lazurio/account/last` once per full page load of Apps, Chat and Automate, fire
and forget and silent on failure, apart from the rail's merge of the account. The
Environment it names is the shell document's `current`, its base host (the
identity of an Environment entry, F37's addendum).
- "Přístup k modulu" (S15, #151): in a module tile's menu for the Organization's
Owners and Stewards (GitHub's `maintain` on the module's declared repository), into
the Organization's Dashboard in the same window.
- **Preview.** A temporary fixture Folder, never a live one: `bun
scripts/preview-launchpad.ts local|hosted cs|en <port>` writes an Organization with
root-level and workspace modules in Workspace, two production repositories in
Productionspace and an `infra` that is not listed, a synthetic home for Files and
Tools whose `gh` answers the Owner question for the example Organization, and with
`hosted` a recorded entry plus a loopback proxy that adds the gateway's Host and
cookie. Screenshots come from Chrome through playwright-core.
scripts/preview-launchpad.ts local|hosted cs|en <port> [owner|steward|member]` writes
an Organization with root-level and workspace modules in Workspace, two production
repositories in Productionspace and an `infra` that is not listed, a synthetic home
for Files and Tools whose `gh` answers for the example Organization as the role says
(an Owner by default; a Steward maintains its workspace modules' repositories), and
with `hosted` a recorded entry plus a loopback proxy that adds the gateway's Host and
cookie. The account is not part of the preview: a browser automation stands in for
the gateway's relay of `/.lazurio/account/…`. Screenshots come from Chrome through
playwright-core.
- **A fork's snippet** is in decision F36 point 5 and in the target shell's "The forks
and `/.lazurio/`": the rail, and the column head at the top of the fork's sidebar.
- **The interface the forks build on** is `src/shell/interface.ts` (version 1, F36
Expand Down
45 changes: 38 additions & 7 deletions scripts/preview-launchpad.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// A preview of the Launchpad (decision F36) against a temporary fixture
// Folder, never a live one: `bun scripts/preview-launchpad.ts local|hosted
// cs|en [port]`. One Organization with root-level and workspace modules in
// cs|en [port] [owner|steward|member]`. The role is what the preview's GitHub
// identity answers for the example Organization: an Owner (the default), a
// Steward (`maintain` on its module repositories) or a member. One
// Organization with root-level and workspace modules in
// Workspace, production repositories in Productionspace and an `infra` the
// Launchpad does not list (and a second Organization locally, for the rail),
// a synthetic home for Files and Tools. Prints one JSON line: `url` to open
Expand Down Expand Up @@ -34,6 +37,10 @@ import { organizationWithEntry } from "../tests/fixtures/machine-bindings";
const mode = process.argv[2] === "hosted" ? "hosted" : "local";
const locale = process.argv[3] === "en" ? "en" : "cs";
const listenPort = Number(process.argv[4] ?? 24611);
const role =
process.argv[5] === "steward" || process.argv[5] === "member"
? process.argv[5]
: "owner";
const host = "vm-01.example.lazurio.io";
const parent = await realpath(
await mkdtemp(join(tmpdir(), "launchpad-preview-")),
Expand Down Expand Up @@ -66,8 +73,15 @@ const root = await writeOrganization(folder, "example_GEN3", {
modules: [
{ id: "mission-control", path: "mission-control" },
{ id: "design-system", path: "design-system" },
{ id: "knowledgebase" },
{ id: "deals" },
// Workspace modules declare their repository, for a Steward's answer.
{
id: "knowledgebase",
slot: { git: { url: "https://github.com/example/knowledgebase.git" } },
},
{
id: "deals",
slot: { git: { url: "https://github.com/example/deals.git" } },
},
{ id: "website", broken: true },
{ id: "brainstorm", apps: false },
],
Expand Down Expand Up @@ -156,8 +170,9 @@ const app = await startLaunchpad(
undefined,
mode === "hosted" ? { fetcher: async () => new Response("ok") } : {},
// The preview's GitHub identity answers the Owner question of the Apps
// home ("+ Nový modul") as an Owner of the example Organization; no real
// account is asked.
// home ("+ Nový modul", "Přístup k modulu") and a Steward's `maintain` on
// the example Organization's repositories by its role; no real account is
// asked.
toolsEnvironmentOf(
{ PATH: join(home, "bin"), HOME: home },
process.platform,
Expand All @@ -166,10 +181,26 @@ const app = await startLaunchpad(
command[2]?.startsWith("user/memberships/orgs/example")
? {
exitCode: 0,
stdout: JSON.stringify({ state: "active", role: "admin" }),
stdout: JSON.stringify({
state: "active",
role: role === "owner" ? "admin" : "member",
}),
stderr: "",
}
: runTool(command, timeoutMs, env),
: command[1] === "api" && command[2]?.startsWith("repos/example/")
? {
exitCode: 0,
stdout: JSON.stringify({
permissions: {
admin: role === "owner",
maintain: role !== "member",
push: true,
pull: true,
},
}),
stderr: "",
}
: runTool(command, timeoutMs, env),
),
{},
undefined,
Expand Down
Loading
Loading