@tangle-network/agent-app 0.46.2 → 0.46.4

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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/dist/assistant/index.js +3 -1
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chunk-KKBTPZIE.js +805 -0
  5. package/dist/chunk-KKBTPZIE.js.map +1 -0
  6. package/dist/{chunk-2RTYQU4W.js → chunk-LEGZX5MQ.js} +150 -4
  7. package/dist/chunk-LEGZX5MQ.js.map +1 -0
  8. package/dist/chunk-RLOHIX5Y.js +36 -0
  9. package/dist/chunk-RLOHIX5Y.js.map +1 -0
  10. package/dist/{chunk-MBKNKAN2.js → chunk-YTEUZXX2.js} +584 -1252
  11. package/dist/chunk-YTEUZXX2.js.map +1 -0
  12. package/dist/mention-editor-GVLG3W7C.js +521 -0
  13. package/dist/mention-editor-GVLG3W7C.js.map +1 -0
  14. package/dist/stories/studio/StudioProviders.d.ts +10 -0
  15. package/dist/stories/studio/fixtures.d.ts +10 -2
  16. package/dist/studio/audio-preview.d.ts +12 -0
  17. package/dist/studio/generation.d.ts +22 -1
  18. package/dist/studio/index.d.ts +2 -0
  19. package/dist/studio/index.js +29 -1
  20. package/dist/studio/ports.d.ts +54 -0
  21. package/dist/studio-react/composer-option-controls.d.ts +19 -8
  22. package/dist/studio-react/download-generations.d.ts +4 -0
  23. package/dist/studio-react/index.d.ts +38 -22
  24. package/dist/studio-react/index.js +2198 -545
  25. package/dist/studio-react/index.js.map +1 -1
  26. package/dist/studio-react/media-tile.d.ts +21 -0
  27. package/dist/studio-react/media-viewer.d.ts +12 -0
  28. package/dist/studio-react/studio-confirm.d.ts +8 -0
  29. package/dist/studio-react/studio-generation-screen.d.ts +42 -0
  30. package/dist/studio-react/studio-history-screen.d.ts +28 -0
  31. package/dist/studio-react/studio-home-screen.d.ts +20 -0
  32. package/dist/studio-react/studio-playback.d.ts +31 -0
  33. package/dist/studio-react/studio-toasts.d.ts +21 -0
  34. package/dist/studio-react/studio.css +142 -72
  35. package/dist/studio-react/use-batch-navigation.d.ts +20 -0
  36. package/dist/studio-react/use-deferred-delete.d.ts +19 -0
  37. package/dist/studio-react/use-generation-history.d.ts +22 -0
  38. package/dist/studio-react/vault-path-popover.d.ts +12 -0
  39. package/dist/teams/drizzle/invitations-schema.d.ts +1 -1
  40. package/dist/teams/drizzle/schema.d.ts +1 -1
  41. package/dist/web-react/chat-composer.d.ts +24 -4
  42. package/dist/web-react/index.d.ts +1 -0
  43. package/dist/web-react/index.js +12 -6
  44. package/dist/web-react/mention-boundaries.d.ts +33 -0
  45. package/dist/web-react/mention-editor.d.ts +84 -0
  46. package/dist/web-react/mention-list.d.ts +35 -0
  47. package/dist/web-react/mention-pill.d.ts +10 -0
  48. package/dist/web-react/mention-serialize.d.ts +44 -0
  49. package/dist/web-react/use-file-mentions.d.ts +28 -14
  50. package/package.json +33 -7
  51. package/dist/chunk-2RTYQU4W.js.map +0 -1
  52. package/dist/chunk-MBKNKAN2.js.map +0 -1
  53. package/dist/studio-react/generation-card.d.ts +0 -9
  54. package/dist/studio-react/generation-detail-modal.d.ts +0 -11
  55. package/dist/studio-react/generation-detail.d.ts +0 -6
  56. package/dist/studio-react/generation-grid.d.ts +0 -15
  57. package/dist/studio-react/library-drawer.d.ts +0 -12
  58. package/dist/studio-react/library-panel.d.ts +0 -15
  59. package/dist/studio-react/result-canvas.d.ts +0 -6
  60. package/dist/studio-react/studio-header.d.ts +0 -5
  61. package/dist/studio-react/studio-sheet.d.ts +0 -12
  62. package/dist/studio-react/studio-workspace.d.ts +0 -21
  63. package/dist/studio-react/type-config.d.ts +0 -11
@@ -0,0 +1,21 @@
1
+ import { type CSSProperties, type JSX } from 'react';
2
+ import { type Generation } from '../studio/generation';
3
+ import type { StudioMediaActions, VaultSaveResult } from '../studio/ports';
4
+ export interface MediaTileProps {
5
+ generation: Generation;
6
+ context: 'home' | 'generation' | 'history';
7
+ onOpen: (generation: Generation) => void;
8
+ actions?: StudioMediaActions;
9
+ /** Generation screen passes the batch aspect; omitted → square. */
10
+ aspectRatio?: number;
11
+ /** 26 for grids (default), 72 for wide generation/viewer tiles. */
12
+ waveformBars?: number;
13
+ selectMode?: boolean;
14
+ selected?: boolean;
15
+ onToggleSelect?: (id: string) => void;
16
+ onRequestDelete?: (generation: Generation) => void;
17
+ onSaved?: (results: readonly VaultSaveResult[]) => void;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ }
21
+ export declare function MediaTile({ generation, context, onOpen, actions, aspectRatio, waveformBars, selectMode, selected, onToggleSelect, onRequestDelete, onSaved, className, style, }: MediaTileProps): JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { type JSX } from 'react';
2
+ import { type Generation, type StudioMediaActions, type VaultSaveResult } from '../studio';
3
+ export interface MediaViewerModalProps {
4
+ generation: Generation | null;
5
+ onClose: () => void;
6
+ actions?: StudioMediaActions;
7
+ /** Screens own the delete confirm; absent hides the Delete button. */
8
+ onRequestDelete?: (generation: Generation) => void;
9
+ /** After a successful save from the footer popover. */
10
+ onSaved?: (results: readonly VaultSaveResult[]) => void;
11
+ }
12
+ export declare function MediaViewerModal({ generation, onClose, actions, onRequestDelete, onSaved, }: MediaViewerModalProps): JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ import { type JSX } from 'react';
2
+ export interface StudioConfirmDialogProps {
3
+ open: boolean;
4
+ count: number;
5
+ onConfirm: () => void;
6
+ onCancel: () => void;
7
+ }
8
+ export declare function StudioConfirmDialog({ open, count, onConfirm, onCancel, }: StudioConfirmDialogProps): JSX.Element | null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The generation screen: ONE batch's results, with the composer docked under
3
+ * them.
4
+ *
5
+ * The screen is a flex column that fills its host — results scroll, the dock
6
+ * does not. The dock band is deliberately OPAQUE (`bg-background`): it sits
7
+ * above the scrolling results and a translucent band would let a tile read
8
+ * through the composer's own card. The 28px fade above it comes from
9
+ * `.studio-dock::before` in `./studio.css`.
10
+ *
11
+ * The dock's measured height is published two ways, because two different
12
+ * consumers need it: as the `--studio-dock-h` custom property on the screen
13
+ * root (host CSS, and anything nested that must clear the band) and through
14
+ * `useStudioToast().setDockLift`, which lifts the toast stack off the composer.
15
+ * The body's own bottom padding is applied from the measured value directly
16
+ * rather than read back through that property — a CSS custom property a
17
+ * component READS must be one `tokens.css` defines (the theme contract), and a
18
+ * dock height is a layout measurement, not a theme token.
19
+ *
20
+ * A prompt sent from the DOCK starts a NEW batch, which is a different screen.
21
+ * This one reports it (`onOpenGeneration`) exactly once per new batch key and
22
+ * lets the host navigate; every row of that batch still flows through
23
+ * `onGenerated` so the host's list stays whole.
24
+ *
25
+ * Assumes a `StudioToastProvider` and a `StudioPlaybackProvider` above it.
26
+ */
27
+ import { type JSX } from 'react';
28
+ import { type Generation, type StudioMediaActions } from '../studio';
29
+ export interface StudioGenerationScreenProps {
30
+ /** Full merged list from the host (`useStudioGenerations`). */
31
+ generations: Generation[];
32
+ batchKey: string;
33
+ onGenerated: (generation: Generation) => void;
34
+ /** A dock submit that starts a NEW batch navigates via the host. */
35
+ onOpenGeneration: (batchKey: string, first: Generation) => void;
36
+ onBack: () => void;
37
+ workspaceId?: string;
38
+ pickReferenceImage?: () => Promise<string | null>;
39
+ actions?: StudioMediaActions;
40
+ className?: string;
41
+ }
42
+ export declare function StudioGenerationScreen({ generations, batchKey, onGenerated, onOpenGeneration, onBack, workspaceId, pickReferenceImage, actions, className, }: StudioGenerationScreenProps): JSX.Element;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The studio history screen — the full media library behind the home screen's
3
+ * "View history" button: search, a media-type filter, cursor-paged infinite
4
+ * scroll, and the multi-select bar that runs download / save-to-vault / delete
5
+ * over a whole selection at once.
6
+ *
7
+ * It ASSUMES a `StudioToastProvider` and a `StudioPlaybackProvider` above it —
8
+ * the deferred delete it drives lives in both (undo toast, stopping audio for a
9
+ * row that is going away), and every screen in this surface makes the same
10
+ * assumption so a route layout can mount the two providers once.
11
+ *
12
+ * The screen never fetches: `fetchPage` is the product's own paged endpoint and
13
+ * `actions` are its media seams. An ABSENT action hides its control rather than
14
+ * rendering a batch button that does nothing.
15
+ */
16
+ import { type JSX } from 'react';
17
+ import { type FetchGenerationsPage, type GenerationPage, type StudioMediaActions } from '../studio/ports';
18
+ export interface StudioHistoryScreenProps {
19
+ fetchPage: FetchGenerationsPage;
20
+ /** SSR/loader page 1 for the DEFAULT view (no search, no type filter). */
21
+ initialPage?: GenerationPage;
22
+ onBack: () => void;
23
+ actions?: StudioMediaActions;
24
+ /** Trailing debounce before a keystroke becomes a fetch. */
25
+ searchDebounceMs?: number;
26
+ className?: string;
27
+ }
28
+ export declare function StudioHistoryScreen({ fetchPage, initialPage, onBack, actions, searchDebounceMs, className, }: StudioHistoryScreenProps): JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { type JSX } from 'react';
2
+ import type { Generation } from '../studio/generation';
3
+ import type { StudioMediaActions } from '../studio/ports';
4
+ export interface StudioHomeScreenProps {
5
+ /** Newest first, from the host loader (host runs useStudioGenerations). */
6
+ generations: Generation[];
7
+ onGenerated: (generation: Generation) => void;
8
+ onOpenGeneration: (batchKey: string, first: Generation) => void;
9
+ onOpenHistory: () => void;
10
+ workspaceId?: string;
11
+ pickReferenceImage?: () => Promise<string | null>;
12
+ actions?: StudioMediaActions;
13
+ recentLimit?: number;
14
+ className?: string;
15
+ }
16
+ /**
17
+ * Studio's create-and-recent-media landing screen. The host must render this
18
+ * inside both `StudioToastProvider` and `StudioPlaybackProvider`.
19
+ */
20
+ export declare function StudioHomeScreen({ generations, onGenerated, onOpenGeneration, onOpenHistory, workspaceId, pickReferenceImage, actions, recentLimit, className, }: StudioHomeScreenProps): JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { type JSX, type ReactNode } from 'react';
2
+ import type { Generation } from '../studio/generation';
3
+ export interface StudioAudioElementLike {
4
+ src: string;
5
+ currentTime: number;
6
+ readonly duration: number;
7
+ play(): Promise<void> | void;
8
+ pause(): void;
9
+ addEventListener(type: string, listener: () => void): void;
10
+ removeEventListener(type: string, listener: () => void): void;
11
+ }
12
+ export interface StudioPlayback {
13
+ activeId: string | null;
14
+ playing: boolean;
15
+ durationSeconds: number;
16
+ play(generation: Generation): void;
17
+ pause(): void;
18
+ toggle(generation: Generation): void;
19
+ stop(): void;
20
+ seekTo(generation: Generation, seconds: number): void;
21
+ seekBy(seconds: number): void;
22
+ getPositionSeconds(): number;
23
+ registerPositionNode(id: string, node: HTMLElement | null): () => void;
24
+ registerTimeNode(node: HTMLElement | null): () => void;
25
+ }
26
+ export declare function formatClock(seconds: number): string;
27
+ export declare function StudioPlaybackProvider(props: {
28
+ children: ReactNode;
29
+ createAudioElement?: () => StudioAudioElementLike;
30
+ }): JSX.Element;
31
+ export declare function useStudioPlayback(): StudioPlayback;
@@ -0,0 +1,21 @@
1
+ import { type JSX, type ReactNode } from 'react';
2
+ export interface StudioToastInput {
3
+ message: string;
4
+ action?: {
5
+ label: string;
6
+ run: () => void;
7
+ };
8
+ durationMs?: number;
9
+ /** Fires exactly once when the toast leaves for ANY reason. */
10
+ onDismiss?: (reason: 'timeout' | 'dismissed' | 'action') => void;
11
+ }
12
+ interface StudioToastContextValue {
13
+ toast: (input: StudioToastInput) => string;
14
+ dismiss: (id: string) => void;
15
+ setDockLift: (px: number | null) => void;
16
+ }
17
+ export declare function StudioToastProvider({ children }: {
18
+ children: ReactNode;
19
+ }): JSX.Element;
20
+ export declare function useStudioToast(): StudioToastContextValue;
21
+ export {};
@@ -1,66 +1,3 @@
1
- /*
2
- * Studio animations: the right-side library sheet slide/fade, the result-canvas
3
- * bloom-in, and the neutral skeleton shimmer. Ships as a raw stylesheet (the
4
- * `./styles` precedent) so apps that don't already define these classes can
5
- * `import '@tangle-network/agent-app/studio-react/styles'` once. Token-driven
6
- * (`--muted`, `--muted-foreground`) so it inherits the consuming app's theme.
7
- */
8
-
9
- /* ── Studio: library sheet + result canvas ── */
10
- @keyframes studio-sheet-in {
11
- from { transform: translateX(100%); }
12
- to { transform: translateX(0); }
13
- }
14
- @keyframes studio-sheet-out {
15
- from { transform: translateX(0); }
16
- to { transform: translateX(100%); }
17
- }
18
- @keyframes studio-overlay-fade {
19
- from { opacity: 0; }
20
- to { opacity: 1; }
21
- }
22
- @keyframes studio-bloom {
23
- from { opacity: 0; transform: translateY(6px) scale(0.99); }
24
- to { opacity: 1; transform: translateY(0) scale(1); }
25
- }
26
-
27
- /* Dedicated classes (not tailwindcss-animate .animate-in) so the right-side
28
- sheet is unaffected by a conflicting .animate-in rule. Driven by Radix's
29
- data-state on the dialog parts. */
30
- .studio-sheet-content[data-state='open'] { animation: studio-sheet-in 0.28s cubic-bezier(0.32, 0.72, 0, 1); }
31
- .studio-sheet-content[data-state='closed'] { animation: studio-sheet-out 0.2s cubic-bezier(0.4, 0, 1, 1); }
32
- .studio-sheet-overlay[data-state='open'] { animation: studio-overlay-fade 0.28s ease-out; }
33
- .studio-sheet-overlay[data-state='closed'] { animation: studio-overlay-fade 0.2s ease-in reverse; }
34
- .studio-bloom { animation: studio-bloom 0.3s cubic-bezier(0.22, 1, 0.36, 1); }
35
-
36
- /* Neutral skeleton shimmer — a soft grey band sweeps the muted surface.
37
- `.shimmer` is an alias so the generation card and result canvas share one
38
- skeleton treatment. */
39
- @keyframes studio-shimmer {
40
- 0% { background-position: 200% 0; }
41
- 100% { background-position: -200% 0; }
42
- }
43
- .studio-shimmer,
44
- .shimmer {
45
- background-color: hsl(var(--muted));
46
- background-image: linear-gradient(
47
- 90deg,
48
- transparent 0%,
49
- hsl(var(--muted-foreground) / 0.22) 50%,
50
- transparent 100%
51
- );
52
- background-size: 200% 100%;
53
- background-repeat: no-repeat;
54
- animation: studio-shimmer 1.7s ease-in-out infinite;
55
- }
56
-
57
- /* Library card entrance. */
58
- @keyframes studio-row-in {
59
- from { opacity: 0; transform: translateY(4px); }
60
- to { opacity: 1; transform: translateY(0); }
61
- }
62
- .animate-row-in { animation: studio-row-in 0.25s ease-out both; }
63
-
64
1
  /* ── Studio composer: the option band ──
65
2
  The control row's middle section scrolls horizontally and must not show a
66
3
  scrollbar (it sits 34px tall inside a chat card, where a bar eats a third of
@@ -112,13 +49,146 @@
112
49
  .studio-send { background-image: var(--studio-send-gradient); }
113
50
 
114
51
  @media (prefers-reduced-motion: reduce) {
115
- .studio-sheet-content[data-state='open'],
116
- .studio-sheet-content[data-state='closed'],
117
- .studio-sheet-overlay[data-state='open'],
118
- .studio-sheet-overlay[data-state='closed'],
119
- .studio-bloom,
120
- .studio-shimmer,
121
- .shimmer,
122
- .studio-pill-in,
123
- .animate-row-in { animation: none; }
52
+ .studio-pill-in { animation: none; }
53
+ }
54
+
55
+ /* ── Layer ladder ─────────────────────────────────────────────────────
56
+ PopoverSurface hardcodes z-[1000] (src/web-react/controls.tsx) and a
57
+ className cannot override it (equal specificity → stylesheet order wins).
58
+ The ladder is built AROUND 1000: the viewer sits below it so the
59
+ save-to-vault popover is clickable from inside the viewer; the delete
60
+ confirm sits above the popover; toasts above everything.
61
+ Pinned by tests/studio/layer-order.test.ts. */
62
+ .studio-layer-viewer { z-index: 900; }
63
+ .studio-layer-confirm { z-index: 1100; }
64
+ .studio-layer-toasts { z-index: 1200; }
65
+
66
+ /* ── Grids ── 3px gaps, square corners, no tile border: at a 3px gap a 1px
67
+ border reads as a hairline seam and the grid stops reading as one sheet. */
68
+ .studio-grid { display: grid; gap: 3px; }
69
+ .studio-grid-library { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
70
+ @media (max-width: 640px) {
71
+ .studio-grid-library { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
72
+ }
73
+
74
+ /* ── Tile ── the scrim/chip/tooltip colors are theme-INVARIANT literals:
75
+ they sit on generated media and stay dark in both themes. */
76
+ .studio-tile { position: relative; isolation: isolate; border-radius: 0; }
77
+ .studio-tile-scrim {
78
+ background: linear-gradient(to top,
79
+ rgba(10,10,20,.74) 0%, rgba(10,10,20,.34) 46%,
80
+ rgba(10,10,20,.30) 66%, rgba(10,10,20,.62) 100%);
81
+ opacity: 0; transition: opacity .18s ease; pointer-events: none;
82
+ }
83
+ .studio-tile:hover .studio-tile-scrim,
84
+ .studio-tile:focus-within .studio-tile-scrim,
85
+ .studio-tile[data-selected='true'] .studio-tile-scrim { opacity: 1; }
86
+ .studio-tile-actions {
87
+ opacity: 0; transform: translateY(-3px);
88
+ transition: opacity .18s ease, transform .18s ease;
89
+ }
90
+ .studio-tile:hover .studio-tile-actions,
91
+ .studio-tile:focus-within .studio-tile-actions { opacity: 1; transform: none; }
92
+ .studio-tile-prompt {
93
+ opacity: 0; transform: translateY(3px);
94
+ transition: opacity .18s ease, transform .18s ease;
95
+ text-shadow: 0 1px 2px rgba(0,0,0,.35);
96
+ }
97
+ .studio-tile:hover .studio-tile-prompt,
98
+ .studio-tile:focus-within .studio-tile-prompt { opacity: 1; transform: none; }
99
+ .studio-tile.studio-has-player .studio-tile-prompt { left: 36px; }
100
+ .studio-ibtn {
101
+ background: rgba(12,13,26,.55); color: #fff;
102
+ -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
103
+ transition: background .12s ease, color .12s ease;
104
+ }
105
+ .studio-ibtn:hover { background: rgba(12,13,26,.78); }
106
+ .studio-ibtn-danger:hover { color: #ff8095; }
107
+ .studio-ibtn::after {
108
+ content: attr(data-tip); position: absolute; top: calc(100% + 6px); right: 0;
109
+ background: rgba(12,13,26,.92); color: #fff;
110
+ font-size: 11px; line-height: 1; padding: 5px 7px; border-radius: 6px;
111
+ white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity .12s ease;
112
+ }
113
+ .studio-ibtn:hover::after, .studio-ibtn:focus-visible::after { opacity: 1; }
114
+ .studio-chip-dark {
115
+ background: rgba(12,13,26,.55); color: #fff;
116
+ -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
117
+ }
118
+ .studio-tile-badges > * { transition: opacity .18s ease; }
119
+ .studio-tile:hover .studio-tile-badges > :not(.studio-play-btn),
120
+ .studio-tile:focus-within .studio-tile-badges > :not(.studio-play-btn) { opacity: 0; }
121
+ .studio-tile-sel { display: none; border: 1.5px solid rgba(255,255,255,.9); }
122
+ .studio-tile[data-selectable='true']:hover .studio-tile-sel,
123
+ .studio-tile[data-selectable='true']:focus-within .studio-tile-sel,
124
+ .studio-selectmode .studio-tile-sel { display: grid; }
125
+ .studio-selectmode .studio-tile-actions,
126
+ .studio-selectmode .studio-play-btn { display: none; }
127
+
128
+ /* ── Audio waveform ── two identical bar layers; the played layer revealed
129
+ by clip-path from a --pos custom property written per animation frame.
130
+ No CSS animation, so a pause freezes exactly and reduced-motion has
131
+ nothing to strip. */
132
+ .studio-wave {
133
+ position: absolute; inset: 0; display: flex; align-items: center;
134
+ justify-content: center; gap: 2px; padding: 0 8%;
135
+ }
136
+ .studio-wave i {
137
+ flex: 1 1 0; min-width: 2px; max-width: 8px;
138
+ border-radius: 4px; background: currentColor;
139
+ }
140
+ .studio-wave-layers { position: absolute; inset: 0; }
141
+ .studio-wave-play { clip-path: inset(0 calc((1 - var(--pos, 0)) * 100%) 0 0); }
142
+ .studio-wave-layers[data-active='true'] .studio-wave-base { opacity: .3; }
143
+
144
+ /* ── Skeleton ── sweep while running; reduced motion gets a gentle pulse,
145
+ NOT nothing — the surface must still read as busy. */
146
+ .studio-skeleton {
147
+ position: relative; overflow: hidden; border-radius: 0;
148
+ background: hsl(var(--accent)); aspect-ratio: var(--r, 1.5);
149
+ }
150
+ .studio-skeleton::after {
151
+ content: ''; position: absolute; inset: 0;
152
+ background: linear-gradient(100deg, transparent 18%, hsl(var(--foreground) / .07) 50%, transparent 82%);
153
+ transform: translateX(-100%);
154
+ animation: studio-sweep 1.4s ease-in-out infinite;
155
+ }
156
+ @keyframes studio-sweep { to { transform: translateX(100%); } }
157
+ @keyframes studio-softpulse { 0%, 100% { opacity: .25; } 50% { opacity: .7; } }
158
+
159
+ /* ── Motion ── */
160
+ .studio-rise { animation: studio-rise .34s cubic-bezier(.2,.7,.3,1) both; }
161
+ @keyframes studio-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
162
+ .studio-toast-in { animation: studio-toast-in .22s cubic-bezier(.2,.7,.3,1) both; }
163
+ .studio-toast-out { animation: studio-toast-out .18s ease both; }
164
+ @keyframes studio-toast-in { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
165
+ @keyframes studio-toast-out { to { opacity: 0; transform: translateY(6px); } }
166
+
167
+ /* ── Viewer backdrop / dock ── */
168
+ .studio-backdrop {
169
+ background: rgba(12,13,26,.46);
170
+ -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
171
+ }
172
+ :root[data-theme='dark'] .studio-backdrop,
173
+ .dark .studio-backdrop { background: rgba(6,6,12,.62); }
174
+ .studio-dock::before {
175
+ content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
176
+ height: 28px; pointer-events: none;
177
+ background: linear-gradient(to top, hsl(var(--background)), transparent);
178
+ }
179
+ /* --pos is written per animation frame by the shared playback state; the
180
+ fill/thumb consume it here so component TSX carries no var() reference
181
+ (the tokens-contract gate treats TSX var() refs as theme tokens). */
182
+ .studio-seek-fill { width: calc(var(--pos, 0) * 100%); }
183
+ .studio-seek-thumb { left: calc(var(--pos, 0) * 100%); }
184
+ .studio-seek:focus-visible {
185
+ outline: 2px solid hsl(var(--primary)); outline-offset: 3px; border-radius: 999px;
186
+ }
187
+
188
+ @media (prefers-reduced-motion: reduce) {
189
+ .studio-rise, .studio-toast-in, .studio-toast-out { animation: none; }
190
+ .studio-skeleton::after {
191
+ transform: none; background: hsl(var(--foreground) / .07);
192
+ animation: studio-softpulse 1.8s ease-in-out infinite;
193
+ }
124
194
  }
@@ -0,0 +1,20 @@
1
+ import { type Generation } from '../studio/generation';
2
+ export interface UseBatchNavigationOptions {
3
+ /** Rows whose batch keys are treated as already seen. Read once, at mount. */
4
+ seed: readonly Generation[];
5
+ /**
6
+ * A batch key that never navigates, read on every call — the generation
7
+ * screen passes its own batch so rows landing for the batch already on
8
+ * screen stay put while a NEW dock submit navigates.
9
+ */
10
+ currentBatchKey?: string;
11
+ onOpenGeneration: (batchKey: string, first: Generation) => void;
12
+ }
13
+ /**
14
+ * Returns a callback the screens wrap around `onGenerated`: the first row of a
15
+ * batch not seen before navigates via `onOpenGeneration`, exactly once per
16
+ * batch — a four-image batch arrives as four rows sharing one batch key and
17
+ * must produce one navigation, and rows merely refreshed from the loader
18
+ * (seeded at mount) must produce none.
19
+ */
20
+ export declare function useBatchNavigation({ seed, currentBatchKey, onOpenGeneration, }: UseBatchNavigationOptions): (generation: Generation) => void;
@@ -0,0 +1,19 @@
1
+ import type { Generation } from '../studio/generation';
2
+ import type { DeleteGenerations } from '../studio/ports';
3
+ export interface UseDeferredDeleteOptions {
4
+ remove: DeleteGenerations;
5
+ /** Undo window AND toast lifetime. Default 3500. */
6
+ undoWindowMs?: number;
7
+ /** After the server call succeeds for a batch. */
8
+ onCommitted?: (ids: readonly string[]) => void;
9
+ /** After a failed server call restored the rows (screens may refetch). */
10
+ onRestoreFailed?: (ids: readonly string[]) => void;
11
+ }
12
+ export interface DeferredDelete {
13
+ /** Rows the screens must filter OUT of every render. Survives refetches. */
14
+ pendingIds: ReadonlySet<string>;
15
+ request: (generations: readonly Generation[]) => void;
16
+ /** Commit every outstanding batch NOW (unmount/navigation). */
17
+ flush: () => void;
18
+ }
19
+ export declare function useDeferredDelete(options: UseDeferredDeleteOptions): DeferredDelete;
@@ -0,0 +1,22 @@
1
+ import type { FetchGenerationsPage, GenerationPage, MediaTypeFilter } from '../studio/ports';
2
+ import { type Generation } from '../studio/generation';
3
+ export interface UseGenerationHistoryOptions {
4
+ fetchPage: FetchGenerationsPage;
5
+ /** ALREADY debounced by the caller. */
6
+ q: string;
7
+ type: MediaTypeFilter;
8
+ /** SSR/loader page 1 for the DEFAULT view only (q === '' && type === 'all'). */
9
+ initialPage?: GenerationPage;
10
+ }
11
+ export interface GenerationHistoryState {
12
+ items: Generation[];
13
+ hasMore: boolean;
14
+ isLoadingFirst: boolean;
15
+ isLoadingMore: boolean;
16
+ isError: boolean;
17
+ loadMore: () => void;
18
+ retry: () => void;
19
+ reload: () => void;
20
+ }
21
+ /** Cursor-paged generation history over the product-supplied data port. */
22
+ export declare function useGenerationHistory({ fetchPage, q, type, initialPage, }: UseGenerationHistoryOptions): GenerationHistoryState;
@@ -0,0 +1,12 @@
1
+ import { type JSX, type RefObject } from 'react';
2
+ import { type Generation } from '../studio';
3
+ export interface VaultPathPopoverProps {
4
+ open: boolean;
5
+ triggerRef: RefObject<HTMLElement | null>;
6
+ panelRef: RefObject<HTMLDivElement | null>;
7
+ generations: readonly Generation[];
8
+ onSubmit: (path: string) => void | Promise<void>;
9
+ onCancel: () => void;
10
+ pending?: boolean;
11
+ }
12
+ export declare function VaultPathPopover({ open, triggerRef, panelRef, generations, onSubmit, onCancel, pending, }: VaultPathPopoverProps): JSX.Element | null;
@@ -130,7 +130,7 @@ export declare function createWorkspaceInvitationTable(opts: CreateWorkspaceInvi
130
130
  tableName: "workspace_invitation";
131
131
  dataType: "string";
132
132
  columnType: "SQLiteText";
133
- data: "admin" | "viewer" | "editor";
133
+ data: "editor" | "admin" | "viewer";
134
134
  driverParam: string;
135
135
  notNull: true;
136
136
  hasDefault: true;
@@ -370,7 +370,7 @@ export declare function createTeamTables(opts: CreateTeamTablesOptions): {
370
370
  tableName: "workspace_member";
371
371
  dataType: "string";
372
372
  columnType: "SQLiteText";
373
- data: "owner" | "admin" | "viewer" | "editor";
373
+ data: "editor" | "owner" | "admin" | "viewer";
374
374
  driverParam: string;
375
375
  notNull: true;
376
376
  hasDefault: true;
@@ -2,8 +2,10 @@
2
2
  * ChatComposer — the shared message input every agent app used to hand-roll:
3
3
  * an auto-resizing textarea (Enter sends, Shift+Enter inserts a newline), an
4
4
  * opt-in attach + drag-and-drop + clipboard-paste surface with pending-file
5
- * chips, a streaming Stop/Send toggle, a slot for inline controls (model
6
- * picker, reasoning effort), and a Cmd/Ctrl+L focus shortcut.
5
+ * chips, an opt-in `@`-mention mode (`mention`) that swaps the textarea for a
6
+ * lazily loaded rich input with atomic mention pills, a streaming Stop/Send
7
+ * toggle, a slot for inline controls (model picker, reasoning effort), and a
8
+ * Cmd/Ctrl+L focus shortcut.
7
9
  *
8
10
  * Files arrive by three routes — the picker dialog, a drop, and a paste — and
9
11
  * all three funnel through `accept` (`./composer-file-accept`) before they
@@ -35,6 +37,7 @@
35
37
  import { type ReactNode } from 'react';
36
38
  import { type ComposerFileRejection } from './composer-file-accept';
37
39
  import { type DictationAudio } from './use-dictation';
40
+ import type { ComposerMentionProp } from './use-file-mentions';
38
41
  /** Prompt-part descriptor an uploaded file carries (the upload route's
39
42
  * `UploadedChatFile.part`), echoed back in the turn body on send. Mirrors
40
43
  * `/chat-routes`' wire shape structurally — no server import here. */
@@ -259,8 +262,25 @@ export interface ChatComposerProps {
259
262
  * status line. It sits outside the controls slot and never shrinks, so a
260
263
  * wrapping picker set cannot push it away. */
261
264
  trailing?: ReactNode;
265
+ /**
266
+ * Opt-in `@`-mentions. Present ⇒ the textarea is swapped for a lazily
267
+ * loaded TipTap rich input that renders mentions as atomic pills and
268
+ * serializes them to `@<id>` in the value; absent ⇒ exactly the plain
269
+ * textarea, with no TipTap in the bundle. Wire `useFileMentions().mention`
270
+ * straight in. The six `@tiptap/*` packages (core, extension-mention, pm,
271
+ * react, starter-kit, suggestion) are OPTIONAL peers — a consumer installs
272
+ * them to use this prop.
273
+ *
274
+ * The rich input owns its own keyboard surface, so `slashCommands` is
275
+ * disabled while `mention` is set rather than left half-armed with a menu
276
+ * no key can reach; no shipped surface combines the two. Seed and
277
+ * failed-send drafts still apply in mention mode, but caret placement (a
278
+ * textarea affordance) degrades to content-only.
279
+ */
280
+ mention?: ComposerMentionProp;
262
281
  /** `/` commands offered when the draft is exactly a leading slash token.
263
- * Omit (or pass []) and `/` types as ordinary text. */
282
+ * Omit (or pass []) and `/` types as ordinary text. Inert while `mention`
283
+ * is set — see {@link mention}. */
264
284
  slashCommands?: SlashCommand[];
265
285
  /** Dictation is opt-in: pass `onDictate` and the action row gains a mic
266
286
  * button (browsers without `MediaRecorder`/`getUserMedia` render none).
@@ -286,4 +306,4 @@ export interface ChatComposerProps {
286
306
  sendVariant?: 'pill' | 'icon';
287
307
  className?: string;
288
308
  }
289
- export declare function ChatComposer({ onSend, onSendParts, onSendFailed, sendFailureMessage, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, contextItems, canSubmitAttachmentsOnly, attachmentsNotReadyMessage, canSubmitWhileBusy, autoFocus, minRows, maxHeight, trailing, slashCommands, onDictate, onDictateError, focusShortcut, floating, sendLabel, sendVariant, className, }: ChatComposerProps): import("react").JSX.Element;
309
+ export declare function ChatComposer({ onSend, onSendParts, onSendFailed, sendFailureMessage, onCancel, isStreaming, disabled, placeholder, value, onValueChange, initialValue, seed, onSeedApplied, controls, controlsPlacement, onAttach, onAttachFolder, pendingFiles, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, contextItems, canSubmitAttachmentsOnly, attachmentsNotReadyMessage, canSubmitWhileBusy, autoFocus, minRows, maxHeight, trailing, mention, slashCommands, onDictate, onDictateError, focusShortcut, floating, sendLabel, sendVariant, className, }: ChatComposerProps): import("react").JSX.Element;
@@ -41,6 +41,7 @@ export * from './durable-interaction-submit';
41
41
  export * from './use-chat-interactions';
42
42
  export * from './use-file-mentions';
43
43
  export * from './chat-mentions';
44
+ export * from './mention-pill';
44
45
  export * from './chat-attachments';
45
46
  export * from './message-attachments';
46
47
  export * from './use-composer-attachments';
@@ -36,7 +36,6 @@ import {
36
36
  RecordGrid,
37
37
  RunDrillIn,
38
38
  SeatPaywall,
39
- SessionHistoryPanel,
40
39
  Sparkline,
41
40
  __resetAttachmentFileCacheForTests,
42
41
  activityTone,
@@ -63,7 +62,6 @@ import {
63
62
  formatInsightDelta,
64
63
  formatModelCost,
65
64
  formatRecordGridValue,
66
- formatSessionTimestamp,
67
65
  formatSparklineValue,
68
66
  formatTokensPerSecond,
69
67
  hasSecretField,
@@ -121,9 +119,6 @@ import {
121
119
  useDictation,
122
120
  useDurablePlanFlow,
123
121
  useFileMentions,
124
- useInfiniteScroll,
125
- useSessionActions,
126
- useSessionHistory,
127
122
  useSmoothText,
128
123
  useThinkingSeconds,
129
124
  validateRecordGridCell,
@@ -137,7 +132,17 @@ import {
137
132
  withoutRecordGridCreated,
138
133
  withoutRecordGridRemoved,
139
134
  withoutRecordGridUpdate
140
- } from "../chunk-MBKNKAN2.js";
135
+ } from "../chunk-YTEUZXX2.js";
136
+ import {
137
+ SessionHistoryPanel,
138
+ formatSessionTimestamp,
139
+ useInfiniteScroll,
140
+ useSessionActions,
141
+ useSessionHistory
142
+ } from "../chunk-KKBTPZIE.js";
143
+ import {
144
+ MENTION_PILL_CLASS
145
+ } from "../chunk-RLOHIX5Y.js";
141
146
  import "../chunk-FBVLEGEG.js";
142
147
  import {
143
148
  EvidenceLineageTable,
@@ -286,6 +291,7 @@ export {
286
291
  InteractionBadge,
287
292
  InteractionPlanCard,
288
293
  InteractionQuestionCard,
294
+ MENTION_PILL_CLASS,
289
295
  MessageAttachments,
290
296
  MissionActivityLane,
291
297
  ModelPicker,