@guuey/agent-layout 0.16.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Loqu, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @guuey/agent-layout
2
+
3
+ **Agent-mode layout** — the layout category for surfaces where an app and its
4
+ agent share one screen: app menus in the upper sidebar, the agent rail below,
5
+ and a content pane whose ground **follows the user's attention** between the
6
+ two. Address the agent and the pane takes the agent's tone; navigate and it
7
+ returns to the app's. One law: _layout follows attention; transport owns the
8
+ stream_ — the tone flips on submit, never on stream events, and interrupting
9
+ is the transport's business.
10
+
11
+ ```tsx
12
+ import {
13
+ AgentModeProvider,
14
+ AgentModeShell,
15
+ AgentModeSidebar,
16
+ SidebarPanel,
17
+ ActivePane,
18
+ bindGuueyChat,
19
+ useAgentMode,
20
+ } from "@guuey/agent-layout/react";
21
+ import "@guuey/agent-layout/styles.css";
22
+
23
+ function Shell() {
24
+ const { dispatch } = useAgentMode();
25
+ return (
26
+ <AgentModeShell>
27
+ <AgentModeSidebar>
28
+ <SidebarPanel section="app">{/* your menus — zero wiring */}</SidebarPanel>
29
+ <SidebarPanel section="agent">
30
+ <GuueyChat {...bindGuueyChat(dispatch)} />
31
+ </SidebarPanel>
32
+ </AgentModeSidebar>
33
+ <ActivePane>{/* your routes / canvas */}</ActivePane>
34
+ </AgentModeShell>
35
+ );
36
+ }
37
+
38
+ <AgentModeProvider mode={mode} navigationKey={pathname} identity={<Logo />}>
39
+ <Shell />
40
+ </AgentModeProvider>;
41
+ ```
42
+
43
+ - **Two tones, calibrated**: the default pair is the founder-certified
44
+ warm-paper/neutral-chrome step, light and dark. Overrides are validated
45
+ against a perceptibility floor (ΔL\* ≥ 6, or a hue-temperature step) —
46
+ two tones that measure alike are rejected with an explanation.
47
+ - **Route-derived follow**: pass your router's location key as
48
+ `navigationKey`; every navigation returns the pane to the app tone with
49
+ zero per-link wiring. Same-page menu clicks are caught by the app panel's
50
+ capture-phase listener.
51
+ - **No stale-content hold**: while the agent has the room and nothing is
52
+ presented yet, the pane shows a working state (your `identity` + pulse),
53
+ never the previous page under the agent's tone.
54
+ - **Mode-aware**: pass the same `mode` you give your chat surface — the lib
55
+ rides your existing light/dark machinery and adds none of its own.
56
+ - **Not a chat dependency**: `bindGuueyChat` pairs with `@guuey/chat`
57
+ structurally; any agent surface can call the machine directly through
58
+ `useAgentMode()`.
59
+
60
+ React DOM only, zero runtime dependencies, `react >= 18` peer.
package/dist/bind.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The `@guuey/chat` bridge (guuey#403 §4) — pairs with the kit WITHOUT
3
+ * importing it. This package deliberately has NO dependency on
4
+ * `@guuey/chat` (the layout stays consumable by a surface that brings its
5
+ * own agent — ggui console's path calls `agentSubmit()` from
6
+ * `useAgentMode()` directly), so the kit's shapes are mirrored here
7
+ * STRUCTURALLY, the fs-contract discipline:
8
+ *
9
+ * - {@link GuueyChatActivityEventShape} mirrors `GuueyChatActivityEvent`
10
+ * (oss/packages/chat/src/react/guuey-chat.tsx — sync comment on the
11
+ * definition points back here);
12
+ * - {@link PlanViewSummaryShape} mirrors the two fields of
13
+ * `PlanViewSummary` this bridge reads (`key`, `origin`).
14
+ *
15
+ * The template's typecheck is the seam prover: it passes GuueyChat's real
16
+ * callbacks into these structural slots — a drift on either side breaks
17
+ * the template build, never silently.
18
+ */
19
+ import type { AgentModeInput } from "./machine.js";
20
+ /**
21
+ * Structural mirror of `GuueyChatActivityEvent` (@guuey/chat). `submit`
22
+ * fires on every successful send — composer, `handle.send`, and the
23
+ * `ui/message` doorbell paths alike; `settled` fires when the invoke
24
+ * returns to `ready` (success, error, and abort all settle).
25
+ */
26
+ export interface GuueyChatActivityEventShape {
27
+ type: "submit" | "settled";
28
+ }
29
+ /** The two roster fields the bridge reads (structural `PlanViewSummary`). */
30
+ export interface PlanViewSummaryShape {
31
+ key: string;
32
+ origin: "live" | "history";
33
+ }
34
+ /** What the bridge needs from the layout side — `useAgentMode().dispatch`. */
35
+ export type AgentModeBinding = (input: AgentModeInput) => void;
36
+ /** The GuueyChat-shaped props {@link bindGuueyChat} returns. */
37
+ export interface GuueyChatBindingProps {
38
+ onActivity: (event: GuueyChatActivityEventShape) => void;
39
+ onViewsChange: (views: readonly PlanViewSummaryShape[]) => void;
40
+ }
41
+ /**
42
+ * Wire a GuueyChat surface into the active-panel machine: spread the
43
+ * returned props onto `<GuueyChat>` (or compose them inside your own
44
+ * handlers — each is a plain function).
45
+ *
46
+ * - `submit` → `agentSubmit` (tone flips ON SUBMIT — the §3 law);
47
+ * - `settled` → `agentSettled` (no tone transition; ends the working
48
+ * state and the streaming flag);
49
+ * - a roster change whose LIVE view count GREW → `agentViewMounted`
50
+ * (the founder's (d): content replaced the working state). Counting
51
+ * growth — not presence — keeps earlier turns' lingering live views
52
+ * and rehydrated history views from clearing a fresh submit's pending
53
+ * window spuriously.
54
+ */
55
+ export declare function bindGuueyChat(dispatch: AgentModeBinding): GuueyChatBindingProps;
56
+ //# sourceMappingURL=bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAE/D,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,KAAK,IAAI,CAAC;CACjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,qBAAqB,CAa/E"}
package/dist/bind.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Wire a GuueyChat surface into the active-panel machine: spread the
3
+ * returned props onto `<GuueyChat>` (or compose them inside your own
4
+ * handlers — each is a plain function).
5
+ *
6
+ * - `submit` → `agentSubmit` (tone flips ON SUBMIT — the §3 law);
7
+ * - `settled` → `agentSettled` (no tone transition; ends the working
8
+ * state and the streaming flag);
9
+ * - a roster change whose LIVE view count GREW → `agentViewMounted`
10
+ * (the founder's (d): content replaced the working state). Counting
11
+ * growth — not presence — keeps earlier turns' lingering live views
12
+ * and rehydrated history views from clearing a fresh submit's pending
13
+ * window spuriously.
14
+ */
15
+ export function bindGuueyChat(dispatch) {
16
+ let liveCount = 0;
17
+ return {
18
+ onActivity: (event) => {
19
+ if (event.type === "submit")
20
+ dispatch({ type: "agentSubmit" });
21
+ else
22
+ dispatch({ type: "agentSettled" });
23
+ },
24
+ onViewsChange: (views) => {
25
+ const next = views.filter((v) => v.origin === "live").length;
26
+ if (next > liveCount)
27
+ dispatch({ type: "agentViewMounted" });
28
+ liveCount = next;
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@guuey/agent-layout` — the agent-mode layout category (guuey#403).
3
+ *
4
+ * Root entry: the pure machine, the tone math, the token names, and the
5
+ * `@guuey/chat` bridge — everything React-free. Components live under
6
+ * `@guuey/agent-layout/react`; the stylesheet under
7
+ * `@guuey/agent-layout/styles.css`.
8
+ */
9
+ export { agentModeReduce, INITIAL_AGENT_MODE_STATE, type ActivePanel, type AgentModeInput, type AgentModeState, } from "./machine.js";
10
+ export { assertToneFloor, DEFAULT_TONES, deriveTones, hexToLab, meetsToneFloor, mixHex, toneDelta, type TonePair, } from "./tones.js";
11
+ export { bindGuueyChat, type AgentModeBinding, type GuueyChatActivityEventShape, type GuueyChatBindingProps, type PlanViewSummaryShape, } from "./bind.js";
12
+ /**
13
+ * The token vocabulary (§2) — one prefix, kebab-case, `-on` suffix keeps
14
+ * each tone's pair adjacent in sorted listings (platform-ruled naming).
15
+ * `--guuey-layout-pane-tone` is LIB-WRITTEN state: apps read it, never set
16
+ * it — overriding it silently breaks the category's defining behavior.
17
+ */
18
+ export declare const LAYOUT_TOKENS: {
19
+ readonly toneUpper: "--guuey-layout-tone-upper";
20
+ readonly toneUpperOn: "--guuey-layout-tone-upper-on";
21
+ readonly toneLower: "--guuey-layout-tone-lower";
22
+ readonly toneLowerOn: "--guuey-layout-tone-lower-on";
23
+ readonly paneTone: "--guuey-layout-pane-tone";
24
+ readonly toneTransition: "--guuey-layout-tone-transition";
25
+ };
26
+ /**
27
+ * The follow fade — founder-certified on the ggui#633 calibration: the
28
+ * eased ~150ms color fade ("arrives fast, lands soft" at panel scale).
29
+ */
30
+ export declare const DEFAULT_TONE_TRANSITION_MS = 150;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,QAAQ,EACR,cAAc,EACd,MAAM,EACN,SAAS,EACT,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AAEnB;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;CAOhB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@guuey/agent-layout` — the agent-mode layout category (guuey#403).
3
+ *
4
+ * Root entry: the pure machine, the tone math, the token names, and the
5
+ * `@guuey/chat` bridge — everything React-free. Components live under
6
+ * `@guuey/agent-layout/react`; the stylesheet under
7
+ * `@guuey/agent-layout/styles.css`.
8
+ */
9
+ export { agentModeReduce, INITIAL_AGENT_MODE_STATE, } from "./machine.js";
10
+ export { assertToneFloor, DEFAULT_TONES, deriveTones, hexToLab, meetsToneFloor, mixHex, toneDelta, } from "./tones.js";
11
+ export { bindGuueyChat, } from "./bind.js";
12
+ /**
13
+ * The token vocabulary (§2) — one prefix, kebab-case, `-on` suffix keeps
14
+ * each tone's pair adjacent in sorted listings (platform-ruled naming).
15
+ * `--guuey-layout-pane-tone` is LIB-WRITTEN state: apps read it, never set
16
+ * it — overriding it silently breaks the category's defining behavior.
17
+ */
18
+ export const LAYOUT_TOKENS = {
19
+ toneUpper: "--guuey-layout-tone-upper",
20
+ toneUpperOn: "--guuey-layout-tone-upper-on",
21
+ toneLower: "--guuey-layout-tone-lower",
22
+ toneLowerOn: "--guuey-layout-tone-lower-on",
23
+ paneTone: "--guuey-layout-pane-tone",
24
+ toneTransition: "--guuey-layout-tone-transition",
25
+ };
26
+ /**
27
+ * The follow fade — founder-certified on the ggui#633 calibration: the
28
+ * eased ~150ms color fade ("arrives fast, lands soft" at panel scale).
29
+ */
30
+ export const DEFAULT_TONE_TRANSITION_MS = 150;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The active-panel machine (guuey#403 §3) — pure data-in/data-out, no React.
3
+ *
4
+ * ONE law (platform's words): **layout follows attention; transport owns
5
+ * the stream.** The tone tracks the user's locus of action — submit flips
6
+ * it ON SUBMIT (not first token, not completion), navigation flips it back,
7
+ * and stream end changes nothing.
8
+ *
9
+ * State beyond the panel itself (the ggui#633 calibration additions):
10
+ *
11
+ * - `streaming` — a turn is in flight. Read by ONE rule only: closing an
12
+ * agent view returns to the menu UNLESS still streaming (the console's
13
+ * shipped behavior).
14
+ * - `pending` — the founder's (d) requirement: on flip to the agent tone
15
+ * with no view mounted yet, the pane must NEVER hold prior page content
16
+ * on the agent ground — it presents the working state immediately.
17
+ * `pending` arms on submit and clears on view-mount, on settle (the
18
+ * answer is in the log even when no view came), and on return-to-menu.
19
+ *
20
+ * The machine is consumed through `useAgentMode()` / the Provider; this
21
+ * module is exported for tests and for non-React hosts that want the exact
22
+ * transition table.
23
+ */
24
+ export type ActivePanel = "app" | "agent";
25
+ export interface AgentModeState {
26
+ activePanel: ActivePanel;
27
+ /** A turn is in flight (submit → settled). */
28
+ streaming: boolean;
29
+ /** Working-state window: submitted, nothing presented yet (founder (d)). */
30
+ pending: boolean;
31
+ }
32
+ export declare const INITIAL_AGENT_MODE_STATE: AgentModeState;
33
+ export type AgentModeInput =
34
+ /** App-side chrome interaction (NavLink click, panel focus) OR a route change. */
35
+ {
36
+ type: "menuInteraction";
37
+ }
38
+ /** The user sent a message — the agent bridge fired. */
39
+ | {
40
+ type: "agentSubmit";
41
+ }
42
+ /** The turn reached `ready`. NO panel transition (platform-ruled: no bounce-back). */
43
+ | {
44
+ type: "agentSettled";
45
+ }
46
+ /** A live view mounted — the working state has been replaced by content. */
47
+ | {
48
+ type: "agentViewMounted";
49
+ }
50
+ /** The user closed an agent view (canvas "back" affordance). */
51
+ | {
52
+ type: "agentViewClosed";
53
+ };
54
+ /**
55
+ * The transition table (§3, verbatim rules). Pure; last input wins — the
56
+ * transition duration is the only debounce (no timers, no queue).
57
+ */
58
+ export declare function agentModeReduce(state: AgentModeState, input: AgentModeInput): AgentModeState;
59
+ //# sourceMappingURL=machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../src/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,wBAAwB,EAAE,cAKtC,CAAC;AAEF,MAAM,MAAM,cAAc;AACxB,kFAAkF;AAChF;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE;AAC7B,wDAAwD;GACtD;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE;AACzB,sFAAsF;GACpF;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE;AAC1B,4EAA4E;GAC1E;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE;AAC9B,gEAAgE;GAC9D;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAEhC;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,cAAc,CAkB5F"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The active-panel machine (guuey#403 §3) — pure data-in/data-out, no React.
3
+ *
4
+ * ONE law (platform's words): **layout follows attention; transport owns
5
+ * the stream.** The tone tracks the user's locus of action — submit flips
6
+ * it ON SUBMIT (not first token, not completion), navigation flips it back,
7
+ * and stream end changes nothing.
8
+ *
9
+ * State beyond the panel itself (the ggui#633 calibration additions):
10
+ *
11
+ * - `streaming` — a turn is in flight. Read by ONE rule only: closing an
12
+ * agent view returns to the menu UNLESS still streaming (the console's
13
+ * shipped behavior).
14
+ * - `pending` — the founder's (d) requirement: on flip to the agent tone
15
+ * with no view mounted yet, the pane must NEVER hold prior page content
16
+ * on the agent ground — it presents the working state immediately.
17
+ * `pending` arms on submit and clears on view-mount, on settle (the
18
+ * answer is in the log even when no view came), and on return-to-menu.
19
+ *
20
+ * The machine is consumed through `useAgentMode()` / the Provider; this
21
+ * module is exported for tests and for non-React hosts that want the exact
22
+ * transition table.
23
+ */
24
+ export const INITIAL_AGENT_MODE_STATE = {
25
+ // A surface opens as the app; the agent earns the room by being addressed.
26
+ activePanel: "app",
27
+ streaming: false,
28
+ pending: false,
29
+ };
30
+ /**
31
+ * The transition table (§3, verbatim rules). Pure; last input wins — the
32
+ * transition duration is the only debounce (no timers, no queue).
33
+ */
34
+ export function agentModeReduce(state, input) {
35
+ switch (input.type) {
36
+ case "menuInteraction":
37
+ // Re-follows the user WITHOUT touching the stream (mid-stream click
38
+ // included — interruption semantics live in transport, never here).
39
+ return { ...state, activePanel: "app", pending: false };
40
+ case "agentSubmit":
41
+ return { activePanel: "agent", streaming: true, pending: true };
42
+ case "agentSettled":
43
+ // Stream end changes nothing about the panel — the tone tracks the
44
+ // user's locus of action, not the agent's state.
45
+ return { ...state, streaming: false, pending: false };
46
+ case "agentViewMounted":
47
+ return { ...state, pending: false };
48
+ case "agentViewClosed":
49
+ // Closing a view returns to the menu UNLESS still streaming.
50
+ return state.streaming ? state : { ...state, activePanel: "app" };
51
+ }
52
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `@guuey/agent-layout/react` — the Provider + primitives (guuey#403 §4).
3
+ * React DOM only, by design: cross-platform semantics live in
4
+ * `@guuey/chat`'s selection contract (portal's consult on the proposal).
5
+ *
6
+ * Engineering shape (ggui#633's scar, adopted): the active-panel state
7
+ * lives in its OWN provider, separate from any chat/transcript state —
8
+ * shells must never re-render per streaming token. The context value
9
+ * changes ONLY on machine transitions; every callback identity is stable.
10
+ */
11
+ import { type HTMLAttributes, type ReactNode } from "react";
12
+ import { type ActivePanel, type AgentModeInput } from "./machine.js";
13
+ import { type TonePair } from "./tones.js";
14
+ export interface AgentModeProviderProps {
15
+ children: ReactNode;
16
+ /**
17
+ * The presentation mode — the SAME host-resolved value the surface
18
+ * passes to `<GuueyChat mode>`; this lib rides the existing mode
19
+ * machinery and adds none of its own (the ggui#633 binding rule: never
20
+ * fixed-dark, never a second mode system). Default `"light"`, matching
21
+ * the kit's own default.
22
+ */
23
+ mode?: "light" | "dark";
24
+ /**
25
+ * Tone override pair for the CURRENT mode (host-palette / app-theme
26
+ * tiers — §2's tier chain). Validated against the perceptibility floor
27
+ * at wiring time (reject-under-floor, explanatory throw). Base defaults
28
+ * (the ggui#633 shipped pair + its dark mirror) apply when absent.
29
+ */
30
+ tones?: TonePair;
31
+ /** The follow fade in ms — default the founder-certified eased 150. */
32
+ transitionMs?: number;
33
+ /**
34
+ * The surface's identity mark (logo / brand node) for the working state
35
+ * (founder (d)): shown with the built-in spinner while the agent has the
36
+ * room but nothing is presented yet.
37
+ */
38
+ identity?: ReactNode;
39
+ /**
40
+ * The route-derived follow signal (ggui#633's scar: hand-wiring
41
+ * per-link missed 20+ surfaces — sub-nav tabs, in-content links,
42
+ * `router.push`). Pass your router's location key (`usePathname()`,
43
+ * `useLocation().key`, …): ANY change dispatches `menuInteraction`.
44
+ * The initial value dispatches nothing.
45
+ */
46
+ navigationKey?: unknown;
47
+ }
48
+ export declare function AgentModeProvider({ children, mode, tones, transitionMs, identity, navigationKey, }: AgentModeProviderProps): ReactNode;
49
+ export interface UseAgentModeResult {
50
+ activePanel: ActivePanel;
51
+ /** A turn is in flight (submit → settled). */
52
+ streaming: boolean;
53
+ /** The founder-(d) working-state window is open. */
54
+ pending: boolean;
55
+ /** The raw machine door — `bindGuueyChat(dispatch)` wires a kit surface. */
56
+ dispatch: (input: AgentModeInput) => void;
57
+ /** Escape hatch for surfaces with custom needs (documented as rarely needed). */
58
+ setActivePanel: (panel: ActivePanel) => void;
59
+ /** The <1024px overlay drawer (lib-owned state; Shell renders the toggle). */
60
+ drawerOpen: boolean;
61
+ setDrawerOpen: (open: boolean) => void;
62
+ }
63
+ export declare function useAgentMode(): UseAgentModeResult;
64
+ /**
65
+ * The grid: sidebar column + pane. Below 1024px (the console family's own
66
+ * sidebar boundary — one muscle memory across surfaces) the sidebar leaves
67
+ * the grid and becomes an overlay drawer, and the follow is SUSPENDED —
68
+ * the pane holds the lower (agent) tone (stylesheet-enforced; §4's ruled
69
+ * degraded mode). The drawer toggle renders only at drawer widths.
70
+ *
71
+ * Children: an {@link AgentModeSidebar} (wrapping the two panels) and an
72
+ * {@link ActivePane}. The wrapper is structural — the drawer must slide as
73
+ * ONE element, so the two panels share a positioned parent (the §4 sketch
74
+ * elides it; the contract is unchanged).
75
+ */
76
+ export declare function AgentModeShell({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>): ReactNode;
77
+ /** The sidebar column: the two panels' shared, drawer-slidable parent. */
78
+ export declare function AgentModeSidebar({ children, className, ...rest }: HTMLAttributes<HTMLElement>): ReactNode;
79
+ export interface SidebarPanelProps extends HTMLAttributes<HTMLDivElement> {
80
+ section: "app" | "agent";
81
+ }
82
+ /**
83
+ * One sidebar section. `section="app"` wires `menuInteraction` on
84
+ * pointer/focus interactions inside it (capture-phase — apps write ZERO
85
+ * per-link wiring; this covers same-page clicks the route signal cannot
86
+ * see). `section="agent"` hosts the agent surface and wires nothing — the
87
+ * agent bridge speaks through the machine.
88
+ */
89
+ export declare function SidebarPanel({ section, children, className, ...rest }: SidebarPanelProps): ReactNode;
90
+ export interface ActivePaneProps extends HTMLAttributes<HTMLDivElement> {
91
+ /**
92
+ * Replaces the built-in working-state treatment (identity + pulse) shown
93
+ * while the agent has the room and nothing is presented yet (founder
94
+ * (d): NEVER hold prior page content on the agent ground).
95
+ */
96
+ workingState?: ReactNode;
97
+ }
98
+ /**
99
+ * The right pane: paints `--guuey-layout-pane-tone`, animates per the
100
+ * transition token, honors `prefers-reduced-motion` (stylesheet: instant
101
+ * snap). While the founder-(d) window is open the pane presents the
102
+ * working state INSTEAD of its children — prior page content never sits
103
+ * on the agent ground.
104
+ */
105
+ export declare function ActivePane({ children, workingState, className, ...rest }: ActivePaneProps): ReactNode;
106
+ export { bindGuueyChat } from "./bind.js";
107
+ export type { AgentModeInput, AgentModeState, ActivePanel } from "./machine.js";
108
+ export type { TonePair } from "./tones.js";
109
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAUL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,cAAc,EAEpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAa3E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,IAAc,EACd,KAAK,EACL,YAAyC,EACzC,QAAe,EACf,aAAa,GACd,EAAE,sBAAsB,GAAG,SAAS,CAiDpC;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C,iFAAiF;IACjF,cAAc,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,YAAY,IAAI,kBAAkB,CAoBjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,cAAc,CAAC,GAAG,SAAS,CAqB5C;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAS,EACT,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG,SAAS,CAUzC;AAED,MAAM,WAAW,iBAAkB,SAAQ,cAAc,CAAC,cAAc,CAAC;IACvE,OAAO,EAAE,KAAK,GAAG,OAAO,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAcpG;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc,CAAC,cAAc,CAAC;IACrE;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,eAAe,GAAG,SAAS,CAwBrG;AAMD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
package/dist/react.js ADDED
@@ -0,0 +1,121 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `@guuey/agent-layout/react` — the Provider + primitives (guuey#403 §4).
4
+ * React DOM only, by design: cross-platform semantics live in
5
+ * `@guuey/chat`'s selection contract (portal's consult on the proposal).
6
+ *
7
+ * Engineering shape (ggui#633's scar, adopted): the active-panel state
8
+ * lives in its OWN provider, separate from any chat/transcript state —
9
+ * shells must never re-render per streaming token. The context value
10
+ * changes ONLY on machine transitions; every callback identity is stable.
11
+ */
12
+ import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState, } from "react";
13
+ import { agentModeReduce, INITIAL_AGENT_MODE_STATE, } from "./machine.js";
14
+ import { assertToneFloor, DEFAULT_TONES } from "./tones.js";
15
+ import { DEFAULT_TONE_TRANSITION_MS, LAYOUT_TOKENS } from "./index.js";
16
+ const AgentModeContext = createContext(null);
17
+ export function AgentModeProvider({ children, mode = "light", tones, transitionMs = DEFAULT_TONE_TRANSITION_MS, identity = null, navigationKey, }) {
18
+ const [state, dispatch] = useReducer(agentModeReduce, INITIAL_AGENT_MODE_STATE);
19
+ const [drawerOpen, setDrawerOpen] = useState(false);
20
+ // Route-derived follow: any CHANGE of the key = the user navigated.
21
+ const navRef = useRef({ initial: true, key: navigationKey });
22
+ useEffect(() => {
23
+ if (navRef.current.initial) {
24
+ navRef.current = { initial: false, key: navigationKey };
25
+ return;
26
+ }
27
+ if (Object.is(navRef.current.key, navigationKey))
28
+ return;
29
+ navRef.current.key = navigationKey;
30
+ dispatch({ type: "menuInteraction" });
31
+ setDrawerOpen(false);
32
+ }, [navigationKey]);
33
+ const resolvedTones = useMemo(() => {
34
+ const pair = tones ?? DEFAULT_TONES[mode];
35
+ // Reject-under-floor at wiring time — an override that measures alike
36
+ // defeats the category (§2); the base defaults pass by construction.
37
+ if (tones !== undefined)
38
+ assertToneFloor(tones);
39
+ return pair;
40
+ }, [tones, mode]);
41
+ const value = useMemo(() => ({ state, dispatch, drawerOpen, setDrawerOpen, identity }), [state, drawerOpen, identity]);
42
+ // Token application (§2): tones + transition as inline custom properties
43
+ // on the shell scope; `pane-tone` is LIB-WRITTEN from the machine state.
44
+ const vars = {
45
+ [LAYOUT_TOKENS.toneUpper]: resolvedTones.upper,
46
+ [LAYOUT_TOKENS.toneUpperOn]: resolvedTones.upperOn,
47
+ [LAYOUT_TOKENS.toneLower]: resolvedTones.lower,
48
+ [LAYOUT_TOKENS.toneLowerOn]: resolvedTones.lowerOn,
49
+ [LAYOUT_TOKENS.paneTone]: state.activePanel === "agent" ? resolvedTones.lower : resolvedTones.upper,
50
+ [LAYOUT_TOKENS.toneTransition]: `${transitionMs}ms`,
51
+ };
52
+ return (_jsx(AgentModeContext.Provider, { value: value, children: _jsx("div", { className: "guuey-agent-layout", "data-mode": mode, "data-active-panel": state.activePanel, style: vars, children: children }) }));
53
+ }
54
+ export function useAgentMode() {
55
+ const ctx = useContext(AgentModeContext);
56
+ if (ctx === null) {
57
+ throw new Error("useAgentMode: no <AgentModeProvider> above this component.");
58
+ }
59
+ const { state, dispatch, drawerOpen, setDrawerOpen } = ctx;
60
+ const setActivePanel = useCallback((panel) => dispatch(panel === "app" ? { type: "menuInteraction" } : { type: "agentSubmit" }), [dispatch]);
61
+ return {
62
+ activePanel: state.activePanel,
63
+ streaming: state.streaming,
64
+ pending: state.pending,
65
+ dispatch,
66
+ setActivePanel,
67
+ drawerOpen,
68
+ setDrawerOpen,
69
+ };
70
+ }
71
+ /**
72
+ * The grid: sidebar column + pane. Below 1024px (the console family's own
73
+ * sidebar boundary — one muscle memory across surfaces) the sidebar leaves
74
+ * the grid and becomes an overlay drawer, and the follow is SUSPENDED —
75
+ * the pane holds the lower (agent) tone (stylesheet-enforced; §4's ruled
76
+ * degraded mode). The drawer toggle renders only at drawer widths.
77
+ *
78
+ * Children: an {@link AgentModeSidebar} (wrapping the two panels) and an
79
+ * {@link ActivePane}. The wrapper is structural — the drawer must slide as
80
+ * ONE element, so the two panels share a positioned parent (the §4 sketch
81
+ * elides it; the contract is unchanged).
82
+ */
83
+ export function AgentModeShell({ children, className, ...rest }) {
84
+ const { drawerOpen, setDrawerOpen } = useAgentMode();
85
+ return (_jsxs("div", { ...rest, className: joinClass("guuey-layout-shell", className), "data-drawer-open": drawerOpen ? "true" : undefined, children: [_jsxs("button", { type: "button", className: "guuey-layout-drawer-toggle", "aria-expanded": drawerOpen, "aria-controls": "guuey-layout-sidebar", onClick: () => setDrawerOpen(!drawerOpen), children: [_jsx("span", { "aria-hidden": "true", children: "\u2630" }), _jsx("span", { className: "guuey-layout-sr-only", children: "Menu" })] }), children] }));
86
+ }
87
+ /** The sidebar column: the two panels' shared, drawer-slidable parent. */
88
+ export function AgentModeSidebar({ children, className, ...rest }) {
89
+ return (_jsx("aside", { ...rest, id: "guuey-layout-sidebar", className: joinClass("guuey-layout-sidebar", className), children: children }));
90
+ }
91
+ /**
92
+ * One sidebar section. `section="app"` wires `menuInteraction` on
93
+ * pointer/focus interactions inside it (capture-phase — apps write ZERO
94
+ * per-link wiring; this covers same-page clicks the route signal cannot
95
+ * see). `section="agent"` hosts the agent surface and wires nothing — the
96
+ * agent bridge speaks through the machine.
97
+ */
98
+ export function SidebarPanel({ section, children, className, ...rest }) {
99
+ const { dispatch } = useAgentMode();
100
+ const onMenuInteraction = section === "app" ? () => dispatch({ type: "menuInteraction" }) : undefined;
101
+ return (_jsx("div", { ...rest, className: joinClass(`guuey-layout-panel guuey-layout-panel-${section}`, className), onPointerDownCapture: onMenuInteraction, onFocusCapture: onMenuInteraction, children: children }));
102
+ }
103
+ /**
104
+ * The right pane: paints `--guuey-layout-pane-tone`, animates per the
105
+ * transition token, honors `prefers-reduced-motion` (stylesheet: instant
106
+ * snap). While the founder-(d) window is open the pane presents the
107
+ * working state INSTEAD of its children — prior page content never sits
108
+ * on the agent ground.
109
+ */
110
+ export function ActivePane({ children, workingState, className, ...rest }) {
111
+ const ctx = useContext(AgentModeContext);
112
+ if (ctx === null)
113
+ throw new Error("ActivePane: no <AgentModeProvider> above this component.");
114
+ const { state, identity } = ctx;
115
+ const working = state.activePanel === "agent" && state.pending;
116
+ return (_jsx("main", { ...rest, className: joinClass("guuey-layout-pane", className), children: working ? ((workingState ?? (_jsxs("div", { className: "guuey-layout-working", role: "status", children: [identity !== null ? _jsx("div", { className: "guuey-layout-working-identity", children: identity }) : null, _jsxs("span", { className: "guuey-layout-working-pulse", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { className: "guuey-layout-sr-only", children: "Working\u2026" })] })))) : (children) }));
117
+ }
118
+ function joinClass(base, extra) {
119
+ return extra === undefined || extra === "" ? base : `${base} ${extra}`;
120
+ }
121
+ export { bindGuueyChat } from "./bind.js";