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
13 changes: 13 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4865,6 +4865,19 @@ prototypes-lazurio `1acd615` (`app/v1/src/Rail.tsx`, `Switcher.tsx`, `EnvPicker.
before). Every interface icon, the shell's and the Launchpad page's, is drawn at the
wireframe's stroke of 1.6 (was 1.5). As the glyph takes the picker's ink, a kind's icon
is light on a dark host without roles (it stayed near-black, issue #169).
*Addendum 2026-10-11 (Anna):* the Launchpad's column narrows from 264 to 256 px (four
grid modules) with 13.5 px entries and a 16 px title, and the picker's name drops to
14 px (`--lz-size-meta`), so a name like "MacBook-Air-2" still fits beside the gear.
Text has three levels, after ClickUp (measured there: #202020, #646464 and
#838383-#8d8d8d): ink for what you act on (a tool's name, a button, the active entry);
muted `gray-500` for what explains it (a settings row's description at 14 px, a
group's label such as Oblíbené or a Tools tier, an inactive entry of the column); and
quiet, halfway between `gray-500` and `gray-400` (3.7:1, as ClickUp's timestamps),
only for text that need not be read (a hint, a neutral state, "Podrobnosti").
Weights between the design system's 400 and 600, since Inter Tight is variable and
read thin beside ChatGPT's 14 px system text: a description at 450, a group's label
and an inactive entry at 500; names, buttons and the active entry stay at 600.
Proposed to the design system as roles for every app.
6. **The keys, with or without the field.** ↓ and ↑ (wrapping), Home, End, Enter and
Escape work in the list. Without the search field the focus lands on the current
Environment, else the first (on "Všechny Organizace" where the space has none), and
Expand Down
14 changes: 10 additions & 4 deletions docs/launchpad-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -1597,12 +1597,18 @@ change (without them; the last known sign-ins stay on the cards) and on "Refresh
status" (with them again), and shows:

- no introduction (root decision 0188, Matěj 2026-10-04: no paragraph that only
restates the page); each tier's note is one short line: "Bez nich tu nic nefunguje.",
"Hodí se skoro každému.", "Zapni jen to, co používáš.";
restates the page), no note under a tier and no line on when the tools were read
(Anna 2026-10-11: each only restated what the page shows); the tier names are quieter
than the tool names they group (muted, 13 px, weight 500; the levels of text are in
decision F36's addendum of 2026-10-11);
- on a shared Environment (the Team preset) the warning that signed-in accounts are
shared by all operators; it is repeated in the confirmation of an enable;
- three groups, **Required**, **Recommended** and **Optional**, in catalog order, each
a settings group of rows. A row carries on the left the tool's real name (gh,
a settings group of rows. A row carries on the left the tool's logo (Anna 2026-10-11: known at first sight;
`src/tools/icons.ts`, bundled simple-icons, CC0, drawn by the same `appMark` as
Integrace; Composio and Codex carry the vendor's own bundled SVG, the OpenAI Blossom
for Codex; executor, without one, shows its first
letter), its real name (gh,
composio, bitwarden, wacli, gogcli, neon: what an agent says and what is searched for), one
plain sentence on what it is for (root decision 0188; `toolDescription` in
`tools-view.ts`, a page-only text: the catalog's `activation.purpose` is rendered into
Expand Down Expand Up @@ -1993,7 +1999,7 @@ the Launchpad builds no viewer of its own.
Integrace and the Marketplace hides it, the rail staying, and shows it again; while
the page steps aside for the panel, the toggle stands in the panel's strip. Its right
edge is a divider like the panel's, 208–480 px and never leaving the page less than
400 px, double-click for 264 px. Below 768 px it stays the sheet opened by the button
400 px, double-click for 256 px. Below 768 px it stays the sheet opened by the button
at the top of the page.
- **Remembered in this browser**: the panel's width or all of the room, the column's
width and whether it is hidden, as one `localStorage` entry, `lazurio.layout`, read
Expand Down
37 changes: 37 additions & 0 deletions src/launchpad/app-mark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { CatalogIcon } from "../integrations/catalog-schema";
import type { MarkSvg } from "../tools/icons";
import { monogram } from "./integrations-view";

/**
* The mark of an app or a tool: its bundled logo in a white square, else its
* first letter. One drawing for Integrace and Settings → Tools, so the same
* app looks the same on both pages. A logo is a simple-icons path in its
* colour, or a vendor's own bundled SVG (Composio).
*/
export function appMark(
name: string,
icon: CatalogIcon | MarkSvg | undefined,
): HTMLElement {
const holder = document.createElement("span");
holder.className = "integration-mark";
holder.setAttribute("aria-hidden", "true");
if (icon === undefined) {
holder.classList.add("integration-mark-letter");
holder.textContent = monogram(name);
return holder;
}
if ("svg" in icon) {
// A bundled constant, parsed as an SVG document, never as HTML.
const parsed = new DOMParser().parseFromString(icon.svg, "image/svg+xml");
holder.append(document.importNode(parsed.documentElement, true));
return holder;
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 24 24");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", icon.path);
path.setAttribute("fill", `#${icon.hex}`);
svg.append(path);
holder.append(svg);
return holder;
}
40 changes: 31 additions & 9 deletions src/launchpad/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@
--input: var(--lz-line);
--muted: var(--lz-gray-25);
--muted-fg: var(--lz-ink-muted);
/* Three levels of text, after ClickUp (Anna 2026-10-11; measured there:
* #202020, #646464 and #838383-#8d8d8d): ink for what you act on (a name,
* the active entry); muted for what explains it (a description, a group's
* label, an inactive entry); quiet for the lowest priority (a hint, a
* neutral state, "Podrobnosti"), halfway between gray-500 and gray-400 at
* 3.7:1, as ClickUp's own timestamps, so used only for text that need not
* be read. */
--quiet-fg: color-mix(in srgb, var(--lz-gray-500), var(--lz-gray-400));
/* Weights between the design system's 400 and 600 (Inter Tight is
* variable): text people read at 450, so the tight face does not read thin
* beside system fonts; an entry of the column and a group's label at 500,
* as ClickUp's sidebar; names, buttons and the active entry stay at 600. */
--weight-text: 450;
--weight-label: 500;
--hover: var(--lz-gray-100);
--link: var(--lz-accent);
--primary: var(--lz-accent-button);
Expand All @@ -76,7 +90,7 @@
--radius-card: var(--lz-radius-md);
--font-sans: var(--lz-font-sans);
--font-mono: var(--lz-font-mono);
--column-width: 264px;
--column-width: 256px;
--chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23707070' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
*, *::before, *::after { box-sizing: border-box; }
Expand Down Expand Up @@ -129,18 +143,18 @@
.column { position: sticky; top: 0; z-index: 20; display: flex; flex: none; flex-direction: column; gap: 10px; width: var(--column-width); height: 100vh; padding: 16px 10px 10px; background: var(--lz-gray-25); border-right: 1px solid var(--lz-line-faint); }
.column-apps, .column-settings { display: flex; flex: 1; min-height: 0; flex-direction: column; gap: 10px; }
.column-scroll { display: flex; flex: 1; min-height: 0; flex-direction: column; gap: 14px; overflow-y: auto; }
.column-title { padding: 0 8px; font-size: 18px; font-weight: var(--lz-weight-title); letter-spacing: var(--lz-track-title); }
.column-title { padding: 0 8px; font-size: 16px; font-weight: var(--lz-weight-title); letter-spacing: var(--lz-track-title); }
.column-label { padding: 4px 10px 6px; color: var(--muted-fg); font-size: 13px; }
.column-favorites { display: grid; gap: 1px; padding-bottom: 8px; border-bottom: 1px solid var(--lz-line-faint); }
.column-hint { padding: 2px 10px 4px; color: var(--muted-fg); font-size: 13px; }
.column-foot { display: grid; gap: 8px; padding-top: 8px; border-top: 1px solid var(--lz-line-faint); }
.column-foot:not(:has(> :not([hidden]))) { display: none; }
.menu { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; }
.menu-item { display: flex; align-items: center; gap: 10px; min-width: 0; height: 34px; padding: 0 10px; border-radius: var(--lz-radius-sm); color: var(--fg); font-size: 14px; text-decoration: none; }
.menu-item { display: flex; align-items: center; gap: 10px; min-width: 0; height: 32px; padding: 0 10px; border-radius: var(--lz-radius-sm); color: var(--fg); font-size: 13.5px; text-decoration: none; }
a.menu-item:hover, button.menu-item:hover { background: var(--lz-gray-50); }
button.menu-item { width: 100%; border: 0; background: transparent; font-weight: 400; cursor: pointer; }
button.menu-item { width: 100%; border: 0; background: transparent; font-weight: var(--weight-label); cursor: pointer; }
.menu-item[aria-current="page"] { background: var(--hover); font-weight: var(--lz-weight-akce); }
.menu-item .icon { width: 17px; height: 17px; }
.menu-item .icon { width: 16px; height: 16px; }
.menu-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-stone { display: block; flex: none; width: 20px; height: 20px; }
.menu-mark { display: grid; flex: none; width: 20px; height: 20px; place-items: center; color: var(--muted-fg); }
Expand Down Expand Up @@ -285,8 +299,8 @@
.row-copy { display: grid; gap: 3px; min-width: 0; }
.row-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; min-height: 1.25rem; color: var(--fg); font-size: 15px; font-weight: var(--lz-weight-akce); }
.row-title code { color: var(--muted-fg); font-weight: 400; }
.row-desc { max-width: 40rem; color: var(--muted-fg); font-size: 13.5px; line-height: 1.45; }
.row-status { color: var(--muted-fg); font-size: 12.5px; }
.row-desc { max-width: 40rem; color: var(--muted-fg); font-size: 14px; font-weight: var(--weight-text); line-height: 1.45; }
.row-status { color: var(--quiet-fg); font-size: 13px; }
.row-control { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 8px; min-width: 0; }
@container (min-width: 32rem) { .row-control { justify-content: flex-end; } }
dl.card { margin: 0; }
Expand All @@ -298,7 +312,7 @@
.callout[data-kind="warning"], .tools-warning { border: 1px solid var(--warning-border); border-radius: var(--radius-control); background: var(--warning-bg); color: var(--fg); padding: 10px 14px; font-size: 14px; }
.callout[data-kind="warning"] strong { color: var(--warning-fg); }
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; color: var(--muted-fg); font-size: 12px; }
details.technical > summary, details.row-details > summary { display: inline-flex; align-items: center; gap: 0.25rem; cursor: pointer; color: var(--muted-fg); font-size: 13px; list-style: none; border-radius: 4px; }
details.technical > summary, details.row-details > summary { display: inline-flex; align-items: center; gap: 0.25rem; cursor: pointer; color: var(--quiet-fg); font-size: 13px; list-style: none; border-radius: 4px; }
details.technical > summary::-webkit-details-marker, details.row-details > summary::-webkit-details-marker { display: none; }
details.technical > summary::before, details.row-details > summary::before { content: ""; width: 0.75rem; height: 0.75rem; background: var(--chevron) center / 0.75rem no-repeat; transform: rotate(-90deg); transition: transform 0.15s; }
details[open].technical > summary::before, details[open].row-details > summary::before { transform: none; }
Expand Down Expand Up @@ -524,6 +538,15 @@
.integration-mark { display: grid; flex: none; width: 40px; height: 40px; place-items: center; border: 1px solid var(--lz-line-faint); border-radius: var(--lz-radius-sm); background: var(--lz-white); }
.integration-mark svg { width: 24px; height: 24px; }
.integration-mark-letter { color: var(--muted-fg); font-weight: var(--lz-weight-akce); font-size: 17px; background: var(--lz-gray-25); }
/* Settings → Tools: the tool's logo beside its name (Anna 2026-10-11), and
* the tier names quieter than the tool names they group. */
.row-ident { display: flex; align-items: flex-start; gap: 12px; min-width: 0; }
.row-ident > .integration-mark { margin-top: 1px; }
#tools .group-head h2, .column-label { color: var(--muted-fg); font-size: 13px; font-weight: var(--weight-label); }
.column-hint, .menu-soon { color: var(--quiet-fg); }
.menu-item { color: var(--muted-fg); font-weight: var(--weight-label); }
.menu-item[aria-current="page"] { font-weight: var(--lz-weight-akce); }
a.menu-item:hover, button.menu-item:hover, .menu-item[aria-current="page"] { color: var(--fg); }
.integration-text { display: grid; gap: 2px; min-width: 0; }
.integration-name { font-size: 15px; font-weight: var(--lz-weight-akce); }
.integration-line { color: var(--muted-fg); font-size: 13px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
Expand Down Expand Up @@ -966,7 +989,6 @@ <h2 id="update-heading" class="sr-only" data-message="updateTitle">Product updat
<section id="tools" data-section="tools" aria-labelledby="page-heading">
<div class="section-head-text">
<p id="tools-shared" class="tools-warning" role="note" hidden data-message="toolsShared">This is the whole Team's shared Environment. Sign in here with team accounts, such as a shared mailbox or calendar. Whatever you sign in here, anyone in the Team can use. Connect your personal accounts in your own Environment.</p>
<p class="toolbar"><span id="tools-checked"></span></p>
<p id="tools-announce" class="sr-only" role="status" aria-live="polite"></p>
</div>
<div id="tools-groups" class="stack"></div>
Expand Down
19 changes: 2 additions & 17 deletions src/launchpad/integrations-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
IntegrationApp,
IntegrationsOverview,
} from "../integrations/model";
import { appMark } from "./app-mark";
import {
addRow,
type CustomForm,
Expand Down Expand Up @@ -37,7 +38,6 @@ import {
cardView,
disconnectText,
integrationsHelp,
monogram,
parseIntegrations,
serverLine,
sourceLines,
Expand Down Expand Up @@ -199,22 +199,7 @@ export function createIntegrationsPanel(

/** The app's mark: its bundled icon, else its first letter. */
function mark(app: IntegrationApp): HTMLElement {
const entry = catalogOf(app.id);
const holder = element("span", "integration-mark");
holder.setAttribute("aria-hidden", "true");
if (entry?.icon === undefined) {
holder.classList.add("integration-mark-letter");
holder.textContent = monogram(app.name);
return holder;
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 24 24");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", entry.icon.path);
path.setAttribute("fill", `#${entry.icon.hex}`);
svg.append(path);
holder.append(svg);
return holder;
return appMark(app.name, catalogOf(app.id)?.icon);
}

const active = () => route !== null;
Expand Down
8 changes: 4 additions & 4 deletions src/launchpad/layout-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const panelMinWidth = 360;
export const pageMinWidth = 400;

/** The left column: T3 Code's narrowest sidebar, which the shell's column
* head is drawn for, the width it had before it could be dragged, and the
* widest that still leaves its navigation a column. */
* head is drawn for, four grid modules by default (Anna 2026-10-11, was
* 264 px), and the widest that still leaves its navigation a column. */
export const columnMinWidth = 208;
export const columnDefaultWidth = 264;
export const columnDefaultWidth = 256;
export const columnMaxWidth = 480;

/** The right panel's width as the person left it: pixels, or all of the
Expand Down Expand Up @@ -159,7 +159,7 @@ export function keyPanel(place: PanelPlace, key: DividerKey): PanelWish | null {
export type ColumnPlace = Readonly<{ width: number; min: number; max: number }>;

/** The column in an app `appWidth` pixels wide (all right of the rail), as
* the person wished, or 264 px: 208–480 px, and never so wide that the page
* the person wished, or 256 px: 208–480 px, and never so wide that the page
* beside it has less than its narrowest width. */
export function placeColumn(
appWidth: number,
Expand Down
8 changes: 0 additions & 8 deletions src/launchpad/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,9 @@ const en = {
toolsLoading: "Reading tools…",
toolsLoadFailed:
"The tools could not be read. Try Refresh status; if that does not help, ask an agent for help.",
toolsChecked: "Checked at {time}",
toolsTierRequired: "Required",
toolsTierRequiredNote: "Nothing works here without them.",
toolsTierRecommended: "Recommended",
toolsTierRecommendedNote: "Useful to almost everyone.",
toolsTierOptional: "Optional",
toolsTierOptionalNote: "Turn on only what you use.",
toolsInstalled: "Installed, version {version}",
toolsInstalledNoVersion: "Installed, version unknown",
toolsNotInstalled: "Not installed",
Expand Down Expand Up @@ -1420,13 +1416,9 @@ const cs: Record<MessageKey, string> = {
toolsLoading: "Načítají se nástroje…",
toolsLoadFailed:
"Nástroje se nepodařilo načíst. Zkuste Obnovit stav; když to nepomůže, požádejte o pomoc agenta.",
toolsChecked: "Zjištěno v {time}",
toolsTierRequired: "Povinné",
toolsTierRequiredNote: "Bez nich tu nic nefunguje.",
toolsTierRecommended: "Doporučené",
toolsTierRecommendedNote: "Hodí se skoro každému.",
toolsTierOptional: "Volitelné",
toolsTierOptionalNote: "Zapni jen to, co používáš.",
toolsInstalled: "Nainstalováno, verze {version}",
toolsInstalledNoVersion: "Nainstalováno, verze neznámá",
toolsNotInstalled: "Není nainstalováno",
Expand Down
Loading
Loading