@fungi.computer/caps 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -550,9 +550,9 @@ marginalia, growth and type tokens, so they look right in `field-guide`,
550
550
  `field-guide-night` and the other editorial themes.
551
551
 
552
552
  - `@fungi.computer/caps/components/field-guide`: `Keycap` (a native keyboard
553
- key), `StatusLed` (a static labelled light), `CartridgeLabel` (a ruled
554
- cartridge face) and `SpecimenLabel` (plate annotation, catalogue metadata,
555
- title and summary).
553
+ key), `CartridgeLabel` (a ruled cartridge face) and `SpecimenLabel` (plate
554
+ annotation, catalogue metadata, title and summary). Status lights are the
555
+ gooey `Status` bead from `@fungi.computer/caps/components/status`.
556
556
  - `@fungi.computer/caps/components/technonomicon`: `CigaretteCard`, `Masthead`,
557
557
  `SectionBar`, `Folio`, `IssueMasthead`, `Spread` and related editorial pieces.
558
558
 
@@ -1006,7 +1006,8 @@ its own control finish.
1006
1006
  ### Editorial pieces
1007
1007
 
1008
1008
  ```tsx
1009
- import { Keycap, StatusLed } from "@fungi.computer/caps/components/field-guide";
1009
+ import { Keycap } from "@fungi.computer/caps/components/field-guide";
1010
+ import { Status } from "@fungi.computer/caps/components/status";
1010
1011
  import {
1011
1012
  Folio,
1012
1013
  Masthead,
@@ -1030,7 +1031,7 @@ export function FrontPage() {
1030
1031
  />
1031
1032
  <p>
1032
1033
  Press <Keycap>J</Keycap> for the next story.{" "}
1033
- <StatusLed active label="Online" />
1034
+ <Status tone="success" label="Online" />
1034
1035
  </p>
1035
1036
  <Folio items={["The Spore Print", "Page A1"]} />
1036
1037
  </>
@@ -5,16 +5,6 @@ export type KeycapProps = {
5
5
  } & HTMLAttributes<HTMLElement>;
6
6
  /** The shared field-guide treatment for a keyboard key. */
7
7
  export declare function Keycap({ children, className, ...props }: KeycapProps): import("react/jsx-runtime").JSX.Element;
8
- /** Semantic accent pigments for the static status light. */
9
- export type StatusLedTone = "spore" | "mycelium" | "fruiting";
10
- /** Props accepted by StatusLed. */
11
- export type StatusLedProps = {
12
- active: boolean;
13
- label: string;
14
- tone?: StatusLedTone;
15
- } & Omit<HTMLAttributes<HTMLSpanElement>, "children">;
16
- /** A static, labelled indicator using the current field-guide pigment tokens. */
17
- export declare function StatusLed({ active, className, label, tone, ...props }: StatusLedProps): import("react/jsx-runtime").JSX.Element;
18
8
  /** Props accepted by CartridgeLabel. */
19
9
  export type CartridgeLabelProps = {
20
10
  title: string;
@@ -4,10 +4,6 @@ import { cn } from "../lib/cn.js";
4
4
  export function Keycap({ children, className, ...props }) {
5
5
  return (_jsx("kbd", { ...props, className: cn("caps-keycap", className), children: children }));
6
6
  }
7
- /** A static, labelled indicator using the current field-guide pigment tokens. */
8
- export function StatusLed({ active, className, label, tone = "mycelium", ...props }) {
9
- return (_jsxs("span", { ...props, "aria-label": `${label}: ${active ? "on" : "off"}`, className: cn("caps-led", className), "data-active": active, "data-tone": tone, role: "img", children: [_jsx("span", { "aria-hidden": "true", className: "caps-led-bead" }), _jsx("span", { "aria-hidden": "true", children: label })] }));
10
- }
11
7
  /** A ruled, token-backed printed label for a physical game cartridge. */
12
8
  export function CartridgeLabel({ className, mark, subtitle, title, ...props }) {
13
9
  return (_jsxs("figure", { ...props, "aria-label": `${title} cartridge label`, className: cn("caps-cartridge-label", className), children: [_jsx("span", { "aria-hidden": "true", className: "caps-cartridge-label-rule" }), mark ? (_jsx("span", { "aria-hidden": "true", className: "caps-cartridge-label-mark", children: mark })) : null, _jsx("span", { className: "caps-cartridge-label-title", children: title }), subtitle ? (_jsx("span", { className: "caps-cartridge-label-subtitle", children: subtitle })) : null, _jsx("span", { "aria-hidden": "true", className: "caps-cartridge-label-rule" })] }));
@@ -0,0 +1,44 @@
1
+ import { type StatusTone } from "./status.js";
2
+ /** One place in the content's path; `siblings` makes it a switcher. */
3
+ export type WindowCrumb = {
4
+ id: string;
5
+ label: string;
6
+ /** Other places at this level, offered by a ▾ switcher on the crumb. */
7
+ siblings?: readonly {
8
+ id: string;
9
+ label: string;
10
+ }[];
11
+ };
12
+ /** A command the content offers in the header; the content owns its effect. */
13
+ export type WindowCommand = {
14
+ id: string;
15
+ label: string;
16
+ /** Which Caps button a primary command wears; default `outline`. */
17
+ variant?: "primary" | "outline" | "ghost";
18
+ /** A menu item that removes or discards something. */
19
+ tone?: "destructive";
20
+ disabled?: boolean;
21
+ };
22
+ /** A quiet fact about the view: muted text led by the Caps status bead. */
23
+ export type WindowStatus = {
24
+ id: string;
25
+ label: string;
26
+ tone?: Extract<StatusTone, "neutral" | "info" | "warning" | "success" | "error">;
27
+ };
28
+ /** The path as a trail; on a phone only `‹ parent › current` remain. */
29
+ export declare function WindowCrumbs({ crumbs, popover, onCrumb, }: {
30
+ crumbs: readonly WindowCrumb[];
31
+ popover?: HTMLElement | null | undefined;
32
+ onCrumb?: ((crumbId: string, siblingId?: string) => void) | undefined;
33
+ }): import("react/jsx-runtime").JSX.Element;
34
+ /** Primary commands as buttons, the rest behind a ⋯ menu. */
35
+ export declare function WindowCommands({ primary, menu, popover, onAction, }: {
36
+ primary?: readonly WindowCommand[] | undefined;
37
+ menu?: readonly WindowCommand[] | undefined;
38
+ popover?: HTMLElement | null | undefined;
39
+ onAction?: ((id: string) => void) | undefined;
40
+ }): import("react/jsx-runtime").JSX.Element | null;
41
+ /** Muted state text with the Caps status bead; hidden on a phone. */
42
+ export declare function WindowStatuses({ status, }: {
43
+ status: readonly WindowStatus[];
44
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown, Ellipsis } from "lucide-react";
3
+ import { Button } from "./button.js";
4
+ import { Status } from "./status.js";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./dropdown-menu.js";
6
+ import { IconButton } from "./icon-button.js";
7
+ /** The longest path shown whole; longer paths fold the middle into "…". */
8
+ const CRUMBS_SHOWN = 4;
9
+ function Separator() {
10
+ return (_jsx("span", { className: "caps-window-crumb-separator", "aria-hidden": "true", children: "\u203A" }));
11
+ }
12
+ function Crumb({ crumb, current, popover, onCrumb, }) {
13
+ const label = _jsx("span", { className: "caps-window-crumb-label", children: crumb.label });
14
+ const common = {
15
+ "data-caps-window-crumb": crumb.id,
16
+ "aria-current": current ? "page" : undefined,
17
+ };
18
+ if (crumb.siblings?.length) {
19
+ return (_jsxs(DropdownMenu, { modal: false, children: [_jsxs(DropdownMenuTrigger, { ...common, className: "caps-window-crumb shrink", render: _jsx(Button, { size: "sm", variant: "ghost" }), "aria-label": `${crumb.label}, switch`, children: [label, _jsx(ChevronDown, { "aria-hidden": "true", className: "size-3" })] }), _jsx(DropdownMenuContent, { align: "start", container: popover, children: crumb.siblings.map((sibling) => (_jsx(DropdownMenuItem, { onSelect: () => {
20
+ onCrumb?.(crumb.id, sibling.id);
21
+ }, children: sibling.label }, sibling.id))) })] }));
22
+ }
23
+ if (current || onCrumb === undefined)
24
+ return (_jsx("span", { ...common, className: "caps-window-crumb", children: label }));
25
+ return (_jsx(Button, { ...common, size: "sm", variant: "ghost", className: "caps-window-crumb shrink", onClick: () => {
26
+ onCrumb(crumb.id);
27
+ }, children: label }));
28
+ }
29
+ /** The path as a trail; on a phone only `‹ parent › current` remain. */
30
+ export function WindowCrumbs({ crumbs, popover, onCrumb, }) {
31
+ const folded = crumbs.length > CRUMBS_SHOWN;
32
+ const head = folded ? crumbs.slice(0, 1) : crumbs.slice(0, -1);
33
+ const middle = folded ? crumbs.slice(1, -2) : [];
34
+ const tail = folded ? crumbs.slice(-2) : crumbs.slice(-1);
35
+ const last = crumbs.at(-1);
36
+ const parts = [];
37
+ const add = (crumb) => {
38
+ parts.push(_jsxs("span", { className: "caps-window-crumb-item", "data-caps-crumb-position": crumb.id === last?.id
39
+ ? "current"
40
+ : crumb.id === crumbs.at(-2)?.id
41
+ ? "parent"
42
+ : "ancestor", children: [parts.length > 0 ? _jsx(Separator, {}) : null, _jsx(Crumb, { crumb: crumb, current: crumb.id === last?.id, popover: popover, onCrumb: onCrumb })] }, crumb.id));
43
+ };
44
+ for (const crumb of head)
45
+ add(crumb);
46
+ if (middle.length > 0)
47
+ parts.push(_jsxs("span", { className: "caps-window-crumb-item", "data-caps-crumb-position": "ancestor", children: [_jsx(Separator, {}), _jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { className: "caps-window-crumb", render: _jsx(Button, { size: "sm", variant: "ghost" }), "aria-label": "More of the path", children: _jsx(Ellipsis, { "aria-hidden": "true", className: "size-3" }) }), _jsx(DropdownMenuContent, { align: "start", container: popover, children: middle.map((crumb) => (_jsx(DropdownMenuItem, { onSelect: () => {
48
+ onCrumb?.(crumb.id);
49
+ }, children: crumb.label }, crumb.id))) })] })] }, "fold"));
50
+ for (const crumb of tail)
51
+ add(crumb);
52
+ return (_jsx("nav", { className: "caps-window-crumbs", "aria-label": "Path", children: parts }));
53
+ }
54
+ /** Primary commands as buttons, the rest behind a ⋯ menu. */
55
+ export function WindowCommands({ primary, menu, popover, onAction, }) {
56
+ if (!primary?.length && !menu?.length)
57
+ return null;
58
+ return (_jsxs("div", { className: "caps-window-commands", role: "group", "aria-label": "Commands", children: [primary?.map((command) => (_jsx(Button, { size: "sm", variant: command.variant ?? "outline", disabled: command.disabled === true, onClick: () => {
59
+ onAction?.(command.id);
60
+ }, children: command.label }, command.id))), menu?.length ? (_jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { render: _jsx(IconButton, { icon: _jsx(Ellipsis, {}), "aria-label": "More" }) }), _jsx(DropdownMenuContent, { align: "end", container: popover, children: menu.map((command) => (_jsx(DropdownMenuItem, { disabled: command.disabled === true, variant: command.tone === "destructive" ? "destructive" : "default", onSelect: () => {
61
+ onAction?.(command.id);
62
+ }, children: command.label }, command.id))) })] })) : null] }));
63
+ }
64
+ /** Muted state text with the Caps status bead; hidden on a phone. */
65
+ export function WindowStatuses({ status, }) {
66
+ if (status.length === 0)
67
+ return null;
68
+ return (_jsx("div", { className: "caps-window-statuses", role: "group", "aria-label": "State", children: status.map((item) => (_jsx(Status, { glyph: "dot", size: "compact", tone: item.tone ?? "neutral", label: item.label, title: item.label, className: "caps-window-status" }, item.id))) }));
69
+ }
@@ -1,4 +1,6 @@
1
1
  import { type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from "react";
2
+ import { type WindowStatus, type WindowCommand, type WindowCrumb } from "./window-crumbs.js";
3
+ export type { WindowCommand, WindowCrumb, WindowStatus, } from "./window-crumbs.js";
2
4
  /** Window presentation only. The host owns geometry, focus order and lifecycle. */
3
5
  export type WindowFrameProps = HTMLAttributes<HTMLElement> & {
4
6
  active?: boolean;
@@ -33,12 +35,30 @@ export type WindowHeaderProps = Omit<HTMLAttributes<HTMLElement>, "title" | "chi
33
35
  actions?: ReactNode;
34
36
  /** Present only while there is somewhere to go back to. */
35
37
  back?: WindowBackAction;
38
+ /**
39
+ * The content's path, shown in place of the title and subtitle. The last
40
+ * crumb is the current place; earlier ones are links, and a crumb with
41
+ * siblings is a switcher.
42
+ */
43
+ crumbs?: readonly WindowCrumb[];
44
+ /** Selected crumb, or the sibling chosen from a switcher crumb. */
45
+ onCrumb?: (crumbId: string, siblingId?: string) => void;
46
+ /** Quiet facts about the view, such as "Valid" or "Saved", with beads. */
47
+ status?: readonly WindowStatus[];
48
+ /** The view's own commands, as buttons before the window controls. */
49
+ primary?: readonly WindowCommand[];
50
+ /** Less common commands, behind a ⋯ menu. */
51
+ menu?: readonly WindowCommand[];
52
+ /** A primary or menu command the person used, by its id. */
53
+ onAction?: (id: string) => void;
54
+ /** Where header menus open; the host's popover layer. */
55
+ popover?: HTMLElement | null;
36
56
  /** `compact` is a slim bar for tiled layouts; touch targets keep 44px. */
37
57
  density?: "regular" | "compact";
38
58
  handleProps?: Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "type">;
39
59
  };
40
60
  /** The handle, Back and actions are siblings, so presses never start a drag. */
41
- export declare function WindowHeader({ title, subtitle, icon, actions, back, density, handleProps, className, ...props }: WindowHeaderProps): import("react/jsx-runtime").JSX.Element;
61
+ export declare function WindowHeader({ title, subtitle, icon, actions, back, crumbs, onCrumb, primary, menu, status, onAction, popover, density, handleProps, className, ...props }: WindowHeaderProps): import("react/jsx-runtime").JSX.Element;
42
62
  /** Available callbacks determine available controls; no inert placeholder actions. */
43
63
  export type WindowActionsProps = {
44
64
  title: string;
@@ -3,6 +3,7 @@ import { ArrowLeft, ArrowUpRight, Columns2, Maximize2, Minimize2, Minus, PanelsT
3
3
  import { forwardRef, } from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { IconButton } from "./icon-button.js";
6
+ import { WindowCommands, WindowCrumbs, WindowStatuses, } from "./window-crumbs.js";
6
7
  /** A clipped, rounded shell for either a tiled or floating application. */
7
8
  export const WindowFrame = forwardRef(function WindowFrame({ active = false, floating = false, fitContent = false, className, ...props }, ref) {
8
9
  return (_jsx("section", { ...props, ref: ref, className: cn("caps-window", className), "data-active": active, "data-floating": floating, "data-fit-content": fitContent }));
@@ -12,10 +13,11 @@ export function WindowSnapPreview({ className, ...props }) {
12
13
  return (_jsx("div", { ...props, "aria-hidden": "true", className: cn("caps-window-snap-preview", className) }));
13
14
  }
14
15
  /** The handle, Back and actions are siblings, so presses never start a drag. */
15
- export function WindowHeader({ title, subtitle, icon, actions, back, density = "regular", handleProps, className, ...props }) {
16
+ export function WindowHeader({ title, subtitle, icon, actions, back, crumbs, onCrumb, primary, menu, status, onAction, popover, density = "regular", handleProps, className, ...props }) {
16
17
  const label = subtitle ? `${title} · ${subtitle}` : title;
17
- const content = (_jsxs(_Fragment, { children: [icon && (_jsx("span", { className: "caps-window-icon", "aria-hidden": "true", children: icon })), _jsx("span", { className: "caps-window-name", children: title }), subtitle && (_jsxs("span", { className: "caps-window-subtitle", children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), " ", subtitle] }))] }));
18
- return (_jsxs("header", { ...props, className: cn("caps-window-header", className), "data-density": density, children: [back && (_jsx(IconButton, { className: "caps-window-back", "aria-label": back.label, title: back.hint ?? back.label, icon: _jsx(ArrowLeft, {}), onClick: back.onBack })), handleProps ? (_jsx("button", { ...handleProps, type: "button", "data-caps-window-handle": true, className: cn("caps-window-title", handleProps.className), title: label, children: content })) : (_jsx("div", { className: "caps-window-title", title: label, children: content })), actions] }));
18
+ const trail = crumbs !== undefined && crumbs.length > 0;
19
+ const content = trail ? null : (_jsxs(_Fragment, { children: [icon && (_jsx("span", { className: "caps-window-icon", "aria-hidden": "true", children: icon })), _jsx("span", { className: "caps-window-name", children: title }), subtitle && (_jsxs("span", { className: "caps-window-subtitle", children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), " ", subtitle] }))] }));
20
+ return (_jsxs("header", { ...props, className: cn("caps-window-header", className), "data-density": density, children: [back && (_jsx(IconButton, { className: "caps-window-back", "aria-label": back.label, title: back.hint ?? back.label, icon: _jsx(ArrowLeft, {}), onClick: back.onBack })), trail ? (_jsxs(_Fragment, { children: [icon && (_jsx("span", { className: "caps-window-icon", "aria-hidden": "true", children: icon })), _jsx(WindowCrumbs, { crumbs: crumbs, popover: popover, onCrumb: onCrumb })] })) : null, handleProps ? (_jsx("button", { ...handleProps, type: "button", "data-caps-window-handle": true, className: cn("caps-window-title", handleProps.className), title: label, ...(trail ? { "aria-label": label } : {}), children: content })) : (_jsx("div", { className: "caps-window-title", title: label, ...(trail ? { "aria-hidden": true } : {}), children: content })), status ? _jsx(WindowStatuses, { status: status }) : null, _jsx(WindowCommands, { primary: primary, menu: menu, popover: popover, onAction: onAction }), actions] }));
19
21
  }
20
22
  /** Consistent icons, ordering and accessible names for host-owned commands. */
21
23
  export function WindowActions({ title, floating = false, maximized = false, onPromote, onToggleFloating, onToggleMaximize, onMinimize, onClose, onSelect, }) {
package/dist/styles.css CHANGED
@@ -5466,9 +5466,6 @@
5466
5466
  .mt-sm {
5467
5467
  margin-top: var(--spacing-sm);
5468
5468
  }
5469
- .mb-xs {
5470
- margin-bottom: var(--spacing-xs);
5471
- }
5472
5469
  .status {
5473
5470
  @layer daisyui.l1.l2.l3 {
5474
5471
  display: inline-block;
@@ -5908,15 +5905,9 @@
5908
5905
  .w-12 {
5909
5906
  width: calc(var(--spacing) * 12);
5910
5907
  }
5911
- .w-20 {
5912
- width: calc(var(--spacing) * 20);
5913
- }
5914
5908
  .w-48 {
5915
5909
  width: calc(var(--spacing) * 48);
5916
5910
  }
5917
- .w-\[calc\(100\%-1rem\)\] {
5918
- width: calc(100% - 1rem);
5919
- }
5920
5911
  .w-auto {
5921
5912
  width: auto;
5922
5913
  }
@@ -5980,12 +5971,12 @@
5980
5971
  .flex-1 {
5981
5972
  flex: 1;
5982
5973
  }
5974
+ .shrink {
5975
+ flex-shrink: 1;
5976
+ }
5983
5977
  .shrink-0 {
5984
5978
  flex-shrink: 0;
5985
5979
  }
5986
- .grow {
5987
- flex-grow: 1;
5988
- }
5989
5980
  .-translate-x-1\/2 {
5990
5981
  --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
5991
5982
  translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -6175,9 +6166,6 @@
6175
6166
  .self-stretch {
6176
6167
  align-self: stretch;
6177
6168
  }
6178
- .justify-self-end {
6179
- justify-self: flex-end;
6180
- }
6181
6169
  .truncate {
6182
6170
  overflow: hidden;
6183
6171
  text-overflow: ellipsis;
@@ -6394,9 +6382,6 @@
6394
6382
  .p-lg {
6395
6383
  padding: var(--spacing-lg);
6396
6384
  }
6397
- .p-lg\! {
6398
- padding: var(--spacing-lg) !important;
6399
- }
6400
6385
  .p-md {
6401
6386
  padding: var(--spacing-md);
6402
6387
  }
@@ -6409,9 +6394,6 @@
6409
6394
  .p-xs {
6410
6395
  padding: var(--spacing-xs);
6411
6396
  }
6412
- .p-xs\! {
6413
- padding: var(--spacing-xs) !important;
6414
- }
6415
6397
  .caps-stat-compact {
6416
6398
  & > .stat {
6417
6399
  padding-block: var(--spacing-xs);
@@ -6448,15 +6430,6 @@
6448
6430
  .py-xs {
6449
6431
  padding-block: var(--spacing-xs);
6450
6432
  }
6451
- .ps-md {
6452
- padding-inline-start: var(--spacing-md);
6453
- }
6454
- .ps-xs {
6455
- padding-inline-start: var(--spacing-xs);
6456
- }
6457
- .pe-sm {
6458
- padding-inline-end: var(--spacing-sm);
6459
- }
6460
6433
  .pt-3xl {
6461
6434
  padding-top: var(--spacing-3xl);
6462
6435
  }
@@ -6582,9 +6555,6 @@
6582
6555
  .whitespace-nowrap {
6583
6556
  white-space: nowrap;
6584
6557
  }
6585
- .whitespace-pre-wrap {
6586
- white-space: pre-wrap;
6587
- }
6588
6558
  .alert-error {
6589
6559
  @layer daisyui.l1.l2 {
6590
6560
  color: var(--color-error-content);
@@ -6771,9 +6741,6 @@
6771
6741
  .underline {
6772
6742
  text-decoration-line: underline;
6773
6743
  }
6774
- .opacity-50 {
6775
- opacity: 50%;
6776
- }
6777
6744
  .opacity-55 {
6778
6745
  opacity: 55%;
6779
6746
  }
@@ -7172,20 +7139,6 @@
7172
7139
  }
7173
7140
  }
7174
7141
  }
7175
- .hover\:gap-md {
7176
- &:hover {
7177
- @media (hover: hover) {
7178
- gap: var(--spacing-md);
7179
- }
7180
- }
7181
- }
7182
- .hover\:gap-sm {
7183
- &:hover {
7184
- @media (hover: hover) {
7185
- gap: var(--spacing-sm);
7186
- }
7187
- }
7188
- }
7189
7142
  .hover\:bg-base-200 {
7190
7143
  &:hover {
7191
7144
  @media (hover: hover) {
@@ -7214,11 +7167,6 @@
7214
7167
  }
7215
7168
  }
7216
7169
  }
7217
- .focus\:gap-xs {
7218
- &:focus {
7219
- gap: var(--spacing-xs);
7220
- }
7221
- }
7222
7170
  .focus-visible\:scale-100 {
7223
7171
  &:focus-visible {
7224
7172
  --tw-scale-x: 100%;
@@ -7373,16 +7321,6 @@
7373
7321
  }
7374
7322
  }
7375
7323
  }
7376
- .data-\[open\]\:bg-base-200 {
7377
- &[data-open] {
7378
- background-color: var(--color-base-200);
7379
- }
7380
- }
7381
- .data-\[open\]\:bg-base-300 {
7382
- &[data-open] {
7383
- background-color: var(--color-base-300);
7384
- }
7385
- }
7386
7324
  .motion-reduce\:transition-none {
7387
7325
  @media (prefers-reduced-motion: reduce) {
7388
7326
  transition-property: none;
@@ -7448,16 +7386,6 @@
7448
7386
  padding-inline: calc(var(--spacing) * 2);
7449
7387
  }
7450
7388
  }
7451
- .md\:px-lg {
7452
- @media (width >= 48rem) {
7453
- padding-inline: var(--spacing-lg);
7454
- }
7455
- }
7456
- .md\:px-xs {
7457
- @media (width >= 48rem) {
7458
- padding-inline: var(--spacing-xs);
7459
- }
7460
- }
7461
7389
  .md\:btn-xs {
7462
7390
  @media (width >= 48rem) {
7463
7391
  @layer daisyui.l1.l2 {
@@ -9152,37 +9080,6 @@
9152
9080
  font-family: var(--font-mono);
9153
9081
  line-height: 1;
9154
9082
  }
9155
- .caps-led {
9156
- display: inline-flex;
9157
- align-items: center;
9158
- gap: var(--spacing-xs);
9159
- color: var(--marginalia-ink);
9160
- font-size: 0.75rem;
9161
- }
9162
- .caps-led-bead {
9163
- width: 0.625rem;
9164
- height: 0.625rem;
9165
- flex: none;
9166
- border: var(--card-rule-width) solid var(--card-rule);
9167
- border-radius: var(--radius-selector);
9168
- background: var(--caps-led-color);
9169
- box-shadow: 0 0 0 var(--card-rule-width) var(--caps-led-color);
9170
- @supports (color: color-mix(in lab, red, red)) {
9171
- box-shadow: 0 0 0 var(--card-rule-width) color-mix(in srgb, var(--caps-led-color) 36%, transparent);
9172
- }
9173
- }
9174
- .caps-led[data-tone="spore"] {
9175
- --caps-led-color: var(--growth-spore);
9176
- }
9177
- .caps-led[data-tone="mycelium"] {
9178
- --caps-led-color: var(--growth-mycelium);
9179
- }
9180
- .caps-led[data-tone="fruiting"] {
9181
- --caps-led-color: var(--growth-fruiting);
9182
- }
9183
- .caps-led[data-active="false"] {
9184
- --caps-led-color: var(--marginalia-ink);
9185
- }
9186
9083
  .caps-cartridge-label {
9187
9084
  display: grid;
9188
9085
  justify-items: center;
@@ -14075,6 +13972,88 @@
14075
13972
  min-height: 44px;
14076
13973
  }
14077
13974
  }
13975
+ .caps-window-header:has(> .caps-window-crumbs) {
13976
+ min-height: 40px;
13977
+ }
13978
+ .caps-window-crumbs {
13979
+ display: flex;
13980
+ align-items: center;
13981
+ min-width: 0;
13982
+ flex-shrink: 1;
13983
+ font-family: var(--caps-label-font, inherit);
13984
+ font-size: var(--text-sm);
13985
+ font-weight: 500;
13986
+ }
13987
+ .caps-window-crumb-item[data-caps-crumb-position="current"] {
13988
+ font-weight: 600;
13989
+ }
13990
+ .caps-window-crumb-item {
13991
+ display: flex;
13992
+ align-items: center;
13993
+ min-width: 0;
13994
+ }
13995
+ .caps-window-crumb-separator {
13996
+ padding-inline: var(--spacing-2xs);
13997
+ color: currentColor;
13998
+ @supports (color: color-mix(in lab, red, red)) {
13999
+ color: color-mix(in srgb, currentColor 50%, transparent);
14000
+ }
14001
+ }
14002
+ .caps-window-crumb {
14003
+ min-width: 0;
14004
+ font-size: inherit;
14005
+ font-weight: inherit;
14006
+ gap: var(--spacing-2xs);
14007
+ }
14008
+ .caps-window-crumb-label {
14009
+ overflow: hidden;
14010
+ text-overflow: ellipsis;
14011
+ white-space: nowrap;
14012
+ }
14013
+ .caps-window-crumb-item[data-caps-crumb-position="current"] .caps-window-crumb {
14014
+ padding-inline: var(--spacing-xs);
14015
+ }
14016
+ .caps-window-crumbs:has(+ .caps-window-title) {
14017
+ max-width: 70%;
14018
+ }
14019
+ .caps-window-statuses {
14020
+ display: flex;
14021
+ align-items: center;
14022
+ gap: var(--spacing-sm);
14023
+ flex-shrink: 0;
14024
+ padding-inline-end: var(--spacing-sm);
14025
+ font-size: var(--text-sm);
14026
+ color: currentColor;
14027
+ @supports (color: color-mix(in lab, red, red)) {
14028
+ color: color-mix(in srgb, currentColor 65%, transparent);
14029
+ }
14030
+ }
14031
+ .caps-window-commands {
14032
+ display: flex;
14033
+ align-items: center;
14034
+ gap: var(--spacing-2xs);
14035
+ flex-shrink: 0;
14036
+ }
14037
+ @media (max-width: 640px) {
14038
+ .caps-window-statuses {
14039
+ gap: var(--spacing-2xs);
14040
+ padding-inline-end: var(--spacing-2xs);
14041
+ }
14042
+ .caps-window-status > span {
14043
+ position: absolute;
14044
+ width: 1px;
14045
+ height: 1px;
14046
+ overflow: hidden;
14047
+ clip-path: inset(50%);
14048
+ white-space: nowrap;
14049
+ }
14050
+ .caps-window-crumb-item[data-caps-crumb-position="ancestor"] {
14051
+ display: none;
14052
+ }
14053
+ .caps-window-crumb-item[data-caps-crumb-position="parent"] .caps-window-crumb-separator {
14054
+ display: none;
14055
+ }
14056
+ }
14078
14057
  .caps-window-body {
14079
14058
  flex: 1;
14080
14059
  min-height: 0;
@@ -15112,9 +15091,6 @@
15112
15091
  font-size: var(--text-xs);
15113
15092
  white-space: nowrap;
15114
15093
  }
15115
- .caps-status-compact .caps-status-bead {
15116
- transform: scale(0.6);
15117
- }
15118
15094
  .caps-action-row-trailing {
15119
15095
  display: flex;
15120
15096
  width: 6rem;
@@ -118,6 +118,7 @@
118
118
  | [components/undo-action](components/undo-action.md) | - |
119
119
  | [components/undo-toast](components/undo-toast.md) | - |
120
120
  | [components/visually-hidden](components/visually-hidden.md) | - |
121
+ | [components/window-crumbs](components/window-crumbs.md) | - |
121
122
  | [components/window](components/window.md) | - |
122
123
  | [components/wizard](components/wizard.md) | - |
123
124
  | [enhance/cigarette-card](enhance/cigarette-card.md) | - |
@@ -26,35 +26,6 @@ A visible keyboard key from a shortcut or instruction.
26
26
 
27
27
  ---
28
28
 
29
- ### StatusLedTone
30
-
31
- ```ts
32
- type StatusLedTone = "spore" | "mycelium" | "fruiting";
33
- ```
34
-
35
- Semantic accent pigments for the static status light.
36
-
37
- ---
38
-
39
- ### StatusLedProps
40
-
41
- ```ts
42
- type StatusLedProps = object &
43
- Omit<HTMLAttributes<HTMLSpanElement>, "children">;
44
- ```
45
-
46
- Props accepted by StatusLed.
47
-
48
- #### Type Declaration
49
-
50
- | Name | Type |
51
- | -------- | --------------------------------- |
52
- | `active` | `boolean` |
53
- | `label` | `string` |
54
- | `tone?` | [`StatusLedTone`](#statusledtone) |
55
-
56
- ---
57
-
58
29
  ### CartridgeLabelProps
59
30
 
60
31
  ```ts
@@ -115,26 +86,6 @@ The shared field-guide treatment for a keyboard key.
115
86
 
116
87
  ---
117
88
 
118
- ### StatusLed()
119
-
120
- ```ts
121
- function StatusLed(__namedParameters): Element;
122
- ```
123
-
124
- A static, labelled indicator using the current field-guide pigment tokens.
125
-
126
- #### Parameters
127
-
128
- | Parameter | Type |
129
- | ------------------- | ----------------------------------- |
130
- | `__namedParameters` | [`StatusLedProps`](#statusledprops) |
131
-
132
- #### Returns
133
-
134
- `Element`
135
-
136
- ---
137
-
138
89
  ### CartridgeLabel()
139
90
 
140
91
  ```ts
@@ -0,0 +1,185 @@
1
+ <!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
2
+
3
+ [**@fungi.computer/caps**](../README.md)
4
+
5
+ ---
6
+
7
+ [@fungi.computer/caps](../README.md) / components/window-crumbs
8
+
9
+ # components/window-crumbs
10
+
11
+ ## Type Aliases
12
+
13
+ ### WindowCrumb
14
+
15
+ ```ts
16
+ type WindowCrumb = object;
17
+ ```
18
+
19
+ One place in the content's path; `siblings` makes it a switcher.
20
+
21
+ #### Properties
22
+
23
+ ##### id
24
+
25
+ ```ts
26
+ id: string;
27
+ ```
28
+
29
+ ##### label
30
+
31
+ ```ts
32
+ label: string;
33
+ ```
34
+
35
+ ##### siblings?
36
+
37
+ ```ts
38
+ optional siblings?: readonly object[];
39
+ ```
40
+
41
+ Other places at this level, offered by a ▾ switcher on the crumb.
42
+
43
+ ---
44
+
45
+ ### WindowCommand
46
+
47
+ ```ts
48
+ type WindowCommand = object;
49
+ ```
50
+
51
+ A command the content offers in the header; the content owns its effect.
52
+
53
+ #### Properties
54
+
55
+ ##### id
56
+
57
+ ```ts
58
+ id: string;
59
+ ```
60
+
61
+ ##### label
62
+
63
+ ```ts
64
+ label: string;
65
+ ```
66
+
67
+ ##### variant?
68
+
69
+ ```ts
70
+ optional variant?: "primary" | "outline" | "ghost";
71
+ ```
72
+
73
+ Which Caps button a primary command wears; default `outline`.
74
+
75
+ ##### tone?
76
+
77
+ ```ts
78
+ optional tone?: "destructive";
79
+ ```
80
+
81
+ A menu item that removes or discards something.
82
+
83
+ ##### disabled?
84
+
85
+ ```ts
86
+ optional disabled?: boolean;
87
+ ```
88
+
89
+ ---
90
+
91
+ ### WindowStatus
92
+
93
+ ```ts
94
+ type WindowStatus = object;
95
+ ```
96
+
97
+ A quiet fact about the view: muted text led by the Caps status bead.
98
+
99
+ #### Properties
100
+
101
+ ##### id
102
+
103
+ ```ts
104
+ id: string;
105
+ ```
106
+
107
+ ##### label
108
+
109
+ ```ts
110
+ label: string;
111
+ ```
112
+
113
+ ##### tone?
114
+
115
+ ```ts
116
+ optional tone?: Extract<StatusTone, "neutral" | "info" | "warning" | "success" | "error">;
117
+ ```
118
+
119
+ ## Functions
120
+
121
+ ### WindowCrumbs()
122
+
123
+ ```ts
124
+ function WindowCrumbs(__namedParameters): Element;
125
+ ```
126
+
127
+ The path as a trail; on a phone only `‹ parent › current` remain.
128
+
129
+ #### Parameters
130
+
131
+ | Parameter | Type |
132
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
133
+ | `__namedParameters` | \{ `crumbs`: readonly [`WindowCrumb`](#windowcrumb)[]; `popover?`: `HTMLElement` \| `null`; `onCrumb?`: (`crumbId`, `siblingId?`) => `void`; \} |
134
+ | `__namedParameters.crumbs` | readonly [`WindowCrumb`](#windowcrumb)[] |
135
+ | `__namedParameters.popover?` | `HTMLElement` \| `null` |
136
+ | `__namedParameters.onCrumb?` | (`crumbId`, `siblingId?`) => `void` |
137
+
138
+ #### Returns
139
+
140
+ `Element`
141
+
142
+ ---
143
+
144
+ ### WindowCommands()
145
+
146
+ ```ts
147
+ function WindowCommands(__namedParameters): Element | null;
148
+ ```
149
+
150
+ Primary commands as buttons, the rest behind a ⋯ menu.
151
+
152
+ #### Parameters
153
+
154
+ | Parameter | Type |
155
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
156
+ | `__namedParameters` | \{ `primary?`: readonly [`WindowCommand`](#windowcommand)[]; `menu?`: readonly [`WindowCommand`](#windowcommand)[]; `popover?`: `HTMLElement` \| `null`; `onAction?`: (`id`) => `void`; \} |
157
+ | `__namedParameters.primary?` | readonly [`WindowCommand`](#windowcommand)[] |
158
+ | `__namedParameters.menu?` | readonly [`WindowCommand`](#windowcommand)[] |
159
+ | `__namedParameters.popover?` | `HTMLElement` \| `null` |
160
+ | `__namedParameters.onAction?` | (`id`) => `void` |
161
+
162
+ #### Returns
163
+
164
+ `Element` \| `null`
165
+
166
+ ---
167
+
168
+ ### WindowStatuses()
169
+
170
+ ```ts
171
+ function WindowStatuses(__namedParameters): Element | null;
172
+ ```
173
+
174
+ Muted state text with the Caps status bead; hidden on a phone.
175
+
176
+ #### Parameters
177
+
178
+ | Parameter | Type |
179
+ | -------------------------- | ----------------------------------------------------------- |
180
+ | `__namedParameters` | \{ `status`: readonly [`WindowStatus`](#windowstatus)[]; \} |
181
+ | `__namedParameters.status` | readonly [`WindowStatus`](#windowstatus)[] |
182
+
183
+ #### Returns
184
+
185
+ `Element` \| `null`
@@ -82,15 +82,22 @@ Supply handleProps only when the title is an interactive layout handle.
82
82
 
83
83
  #### Type Declaration
84
84
 
85
- | Name | Type | Description |
86
- | -------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
87
- | `title` | `string` | - |
88
- | `subtitle?` | `string` | Where the content is now, shown after the title and truncated first. |
89
- | `icon?` | `ReactNode` | - |
90
- | `actions?` | `ReactNode` | - |
91
- | `back?` | [`WindowBackAction`](#windowbackaction) | Present only while there is somewhere to go back to. |
92
- | `density?` | `"regular"` \| `"compact"` | `compact` is a slim bar for tiled layouts; touch targets keep 44px. |
93
- | `handleProps?` | `Omit`\<`ButtonHTMLAttributes`\<`HTMLButtonElement`\>, `"children"` \| `"type"`\> | - |
85
+ | Name | Type | Description |
86
+ | -------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | `title` | `string` | - |
88
+ | `subtitle?` | `string` | Where the content is now, shown after the title and truncated first. |
89
+ | `icon?` | `ReactNode` | - |
90
+ | `actions?` | `ReactNode` | - |
91
+ | `back?` | [`WindowBackAction`](#windowbackaction) | Present only while there is somewhere to go back to. |
92
+ | `crumbs?` | readonly [`WindowCrumb`](window-crumbs.md#windowcrumb)[] | The content's path, shown in place of the title and subtitle. The last crumb is the current place; earlier ones are links, and a crumb with siblings is a switcher. |
93
+ | `onCrumb()?` | (`crumbId`, `siblingId?`) => `void` | Selected crumb, or the sibling chosen from a switcher crumb. |
94
+ | `status?` | readonly [`WindowStatus`](window-crumbs.md#windowstatus)[] | Quiet facts about the view, such as "Valid" or "Saved", with beads. |
95
+ | `primary?` | readonly [`WindowCommand`](window-crumbs.md#windowcommand)[] | The view's own commands, as buttons before the window controls. |
96
+ | `menu?` | readonly [`WindowCommand`](window-crumbs.md#windowcommand)[] | Less common commands, behind a ⋯ menu. |
97
+ | `onAction()?` | (`id`) => `void` | A primary or menu command the person used, by its id. |
98
+ | `popover?` | `HTMLElement` \| `null` | Where header menus open; the host's popover layer. |
99
+ | `density?` | `"regular"` \| `"compact"` | `compact` is a slim bar for tiled layouts; touch targets keep 44px. |
100
+ | `handleProps?` | `Omit`\<`ButtonHTMLAttributes`\<`HTMLButtonElement`\>, `"children"` \| `"type"`\> | - |
94
101
 
95
102
  ---
96
103
 
@@ -289,3 +296,21 @@ Consistent icons, ordering and accessible names for host-owned commands.
289
296
  #### Returns
290
297
 
291
298
  `Element`
299
+
300
+ ## References
301
+
302
+ ### WindowCommand
303
+
304
+ Re-exports [WindowCommand](window-crumbs.md#windowcommand)
305
+
306
+ ---
307
+
308
+ ### WindowCrumb
309
+
310
+ Re-exports [WindowCrumb](window-crumbs.md#windowcrumb)
311
+
312
+ ---
313
+
314
+ ### WindowStatus
315
+
316
+ Re-exports [WindowStatus](window-crumbs.md#windowstatus)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/caps",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Product-neutral React primitives and blocks for Fungi interfaces",
6
6
  "type": "module",
@@ -469,8 +469,8 @@
469
469
  },
470
470
  "devDependencies": {
471
471
  "@fungi.computer/maosaico": "0.0.1",
472
- "@fungi.computer/sprite": "0.0.1",
473
- "@fungi.computer/sprite-zdog": "0.0.1",
472
+ "@fungi.computer/sprite": "0.1.0",
473
+ "@fungi.computer/sprite-zdog": "0.1.0",
474
474
  "@ladle/react": "5.1.1",
475
475
  "@tailwindcss/cli": "4.2.1",
476
476
  "@types/react": "18.3.3",
@@ -487,7 +487,7 @@
487
487
  "typescript": "5.9.3",
488
488
  "vite": "8.2.2",
489
489
  "vitest": "4.1.11",
490
- "@fungi.computer/sigils": "0.0.1",
490
+ "@fungi.computer/sigils": "0.1.0",
491
491
  "postcss-value-parser": "4.2.0",
492
492
  "@tanstack/react-form": "1.33.2",
493
493
  "effect": "4.0.2",
@@ -533,9 +533,6 @@
533
533
  "react"
534
534
  ],
535
535
  "peerDependenciesMeta": {
536
- "@tanstack/react-form": {
537
- "optional": true
538
- },
539
536
  "effect": {
540
537
  "optional": true
541
538
  }
package/src/styles.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Caps' single style entry. Stipe owns Daisy's theme engine and all shared
3
- * semantic values; Caps only asks Tailwind to scan its editable source.
3
+ * semantic values; Caps only asks Tailwind to scan its own components.
4
4
  * Caps owns reading rhythm through Tailwind Typography's `prose`.
5
5
  */
6
6
  @import "@fungi.computer/stipe/styles.css";
@@ -9,7 +9,13 @@
9
9
 
10
10
  @plugin "@tailwindcss/typography";
11
11
 
12
+ /* In the workspace Tailwind scans the component source; published, this
13
+ * entry ships without it, and the compiled components in dist carry the same
14
+ * class names. Tests ship in neither, so neither build styles them. */
12
15
  @source "./";
16
+ @source "../dist/**/*.js";
17
+ @source not "./**/*.test.tsx";
18
+ @source not "./test-utils";
13
19
 
14
20
  /* The themed brand fill stays untouched. Link ink and focus rings keep the
15
21
  primary's hue and chroma and only move lightness toward the ink side of
@@ -411,35 +417,6 @@
411
417
  font-family: var(--font-mono);
412
418
  line-height: 1;
413
419
  }
414
- .caps-led {
415
- display: inline-flex;
416
- align-items: center;
417
- gap: var(--spacing-xs);
418
- color: var(--marginalia-ink);
419
- font-size: 0.75rem;
420
- }
421
- .caps-led-bead {
422
- width: 0.625rem;
423
- height: 0.625rem;
424
- flex: none;
425
- border: var(--card-rule-width) solid var(--card-rule);
426
- border-radius: var(--radius-selector);
427
- background: var(--caps-led-color);
428
- box-shadow: 0 0 0 var(--card-rule-width)
429
- color-mix(in srgb, var(--caps-led-color) 36%, transparent);
430
- }
431
- .caps-led[data-tone="spore"] {
432
- --caps-led-color: var(--growth-spore);
433
- }
434
- .caps-led[data-tone="mycelium"] {
435
- --caps-led-color: var(--growth-mycelium);
436
- }
437
- .caps-led[data-tone="fruiting"] {
438
- --caps-led-color: var(--growth-fruiting);
439
- }
440
- .caps-led[data-active="false"] {
441
- --caps-led-color: var(--marginalia-ink);
442
- }
443
420
  .caps-cartridge-label {
444
421
  display: grid;
445
422
  justify-items: center;
@@ -5849,6 +5826,90 @@
5849
5826
  min-height: 44px;
5850
5827
  }
5851
5828
  }
5829
+ /* The path trail: flat text crumbs on the header face, no new colours.
5830
+ A header that carries a path reads at body size, one control high. */
5831
+ .caps-window-header:has(> .caps-window-crumbs) {
5832
+ min-height: 40px;
5833
+ }
5834
+ .caps-window-crumbs {
5835
+ display: flex;
5836
+ align-items: center;
5837
+ min-width: 0;
5838
+ flex-shrink: 1;
5839
+ font-family: var(--caps-label-font, inherit);
5840
+ font-size: var(--text-sm);
5841
+ font-weight: 500;
5842
+ }
5843
+ .caps-window-crumb-item[data-caps-crumb-position="current"] {
5844
+ font-weight: 600;
5845
+ }
5846
+ .caps-window-crumb-item {
5847
+ display: flex;
5848
+ align-items: center;
5849
+ min-width: 0;
5850
+ }
5851
+ .caps-window-crumb-separator {
5852
+ padding-inline: var(--spacing-2xs);
5853
+ color: color-mix(in srgb, currentColor 50%, transparent);
5854
+ }
5855
+ .caps-window-crumb {
5856
+ min-width: 0;
5857
+ font-size: inherit;
5858
+ font-weight: inherit;
5859
+ gap: var(--spacing-2xs);
5860
+ }
5861
+ .caps-window-crumb-label {
5862
+ overflow: hidden;
5863
+ text-overflow: ellipsis;
5864
+ white-space: nowrap;
5865
+ }
5866
+ .caps-window-crumb-item[data-caps-crumb-position="current"]
5867
+ .caps-window-crumb {
5868
+ padding-inline: var(--spacing-xs);
5869
+ }
5870
+ /* The trail keeps its width beside the empty title handle (a zero basis),
5871
+ and gives way, crumb labels first, only when the row runs out of room. */
5872
+ .caps-window-crumbs:has(+ .caps-window-title) {
5873
+ max-width: 70%;
5874
+ }
5875
+ .caps-window-statuses {
5876
+ display: flex;
5877
+ align-items: center;
5878
+ gap: var(--spacing-sm);
5879
+ flex-shrink: 0;
5880
+ padding-inline-end: var(--spacing-sm);
5881
+ font-size: var(--text-sm);
5882
+ color: color-mix(in srgb, currentColor 65%, transparent);
5883
+ }
5884
+ .caps-window-commands {
5885
+ display: flex;
5886
+ align-items: center;
5887
+ gap: var(--spacing-2xs);
5888
+ flex-shrink: 0;
5889
+ }
5890
+ /* A phone keeps `‹ parent › current`; the rest of the trail is a tap away on Back. */
5891
+ @media (max-width: 640px) {
5892
+ /* State shrinks to its beads; the words stay for screen readers and a long press. */
5893
+ .caps-window-statuses {
5894
+ gap: var(--spacing-2xs);
5895
+ padding-inline-end: var(--spacing-2xs);
5896
+ }
5897
+ .caps-window-status > span {
5898
+ position: absolute;
5899
+ width: 1px;
5900
+ height: 1px;
5901
+ overflow: hidden;
5902
+ clip-path: inset(50%);
5903
+ white-space: nowrap;
5904
+ }
5905
+ .caps-window-crumb-item[data-caps-crumb-position="ancestor"] {
5906
+ display: none;
5907
+ }
5908
+ .caps-window-crumb-item[data-caps-crumb-position="parent"]
5909
+ .caps-window-crumb-separator {
5910
+ display: none;
5911
+ }
5912
+ }
5852
5913
  .caps-window-body {
5853
5914
  flex: 1;
5854
5915
  min-height: 0;
@@ -7063,9 +7124,6 @@
7063
7124
  font-size: var(--text-xs);
7064
7125
  white-space: nowrap;
7065
7126
  }
7066
- .caps-status-compact .caps-status-bead {
7067
- transform: scale(0.6);
7068
- }
7069
7127
  .caps-action-row-trailing {
7070
7128
  display: flex;
7071
7129
  width: 6rem;