@lotics/ui 4.6.0 → 4.6.1

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/AGENTS.md CHANGED
@@ -222,8 +222,15 @@ accepts / edits / dismisses, the deterministic app applies. The agent owns judgm
222
222
  (recognition, estimation, intent→parameters); the app owns geometry, math, and the write.
223
223
  Compose the surfaces as a loop, and reach for the right one by job:
224
224
 
225
- - **Command** — `PromptField`, NOT a chat composer. A prompt TRIGGERS agent work (Enter sends);
226
- while it runs the field locks and the send spins. The imperative "do this" surface.
225
+ - **Command** — `PromptField`, a single-row pill for a TEXT-ONLY trigger (Enter sends; the field
226
+ locks + the send spins while it runs). The imperative "do this" surface. Its `onAttach` is a bare
227
+ trigger with NO review step, so it CANNOT do attach→review→submit — **never auto-run an agent on
228
+ attach.** When a run needs a FILE + a prompt together (attach a photo, review/remove it, add a
229
+ note, THEN send), reach for **`Composer`** instead: fill `actionsButton` with the attach button,
230
+ the `files` slot with `FileThumbnail`s (each `onRemove`-able), and set `sendDisabled` so Send fires
231
+ with an attachment and/or text (`onSend(text)` — the host already holds the uploaded file id). The
232
+ canvas (`tpl_dieline`) uses `Composer` for exactly this; `Composer` morphs into `AgentProgress`
233
+ while running just like the pill.
227
234
  - **Show the work** — `AgentRun`: a live feed of the agent's steps (reasoning, tool calls) on a
228
235
  spine, the running node a pulsing ink dot (a done step settles to a hollow ring). Transparent
229
236
  work, NEVER a bare spinner — the
@@ -252,8 +259,9 @@ Compose the surfaces as a loop, and reach for the right one by job:
252
259
  Ten shapes prove the range. The first four PRODUCE — pick by whether the work is a living artifact,
253
260
  a conversation, a single pass, or generated prose:
254
261
  - **Canvas** (`tpl_dieline`) — the page IS the design on a pannable/zoomable surface: it stays
255
- CENTRED at every zoom (a floating zoom pill bottom-left), the FLOATING composer at the bottom
256
- morphs into `AgentProgress` while running, and a pinned PARAMS PANEL (the `RecordReview` field-card
262
+ CENTRED at every zoom (a floating zoom pill bottom-left), the FLOATING composer at the bottom (a
263
+ `Composer`: attach a photo review/remove it → add a note send, with `sendDisabled` allowing a
264
+ file-and/or-text submit — NOT auto-run on attach) morphs into `AgentProgress` while running, and a pinned PARAMS PANEL (the `RecordReview` field-card
257
265
  in live-edit mode, carrying the single Download; minimizes to a pill) floats centre-right. Panel, zoom pill and composer
258
266
  float ON TOP — they never shift the design. (Floating layers use a `pointerEvents:"none"` wrapper
259
267
  with the interactive child set `"auto"`; RN-Web ignores `"box-none"` in style, so a full-width
@@ -309,8 +317,9 @@ controls — while **status/severity/diffs use functional colour** the way the r
309
317
 
310
318
  Card chrome (borders, microlabels) stays neutral — colour marks the *state*, never the container.
311
319
  The **composer keeps its icons**: `PromptField` is a single-row pill with circular attach + send
312
- `Button`s; `AgentProgress` is the `WaveAvatar` pill. "No icons" was only ever about the review
313
- surfaces' sparkles/severity glyphs, not functional affordances.
320
+ `Button`s; `Composer` (the file+prompt variant) shows an attach `actionsButton` + a `FileThumbnail`
321
+ attachment row (each `onRemove`-able) + send; `AgentProgress` is the `WaveAvatar` pill. "No icons"
322
+ was only ever about the review surfaces' sparkles/severity glyphs, not functional affordances.
314
323
 
315
324
  ---
316
325
 
@@ -8,9 +8,19 @@ import { PressableHighlight } from "@lotics/ui/pressable_highlight";
8
8
  import { DotsIndicator } from "@lotics/ui/dots_indicator";
9
9
  import { InlineTextInput } from "@lotics/ui/inline_text_input";
10
10
  import { AgentProgress } from "@lotics/ui/agent_progress";
11
- import { PromptField } from "@lotics/ui/prompt_field";
11
+ import { Composer } from "@lotics/ui/composer";
12
+ import { FileThumbnail, type DisplayFile } from "@lotics/ui/file_thumbnail";
12
13
  import { type AgentRunStep } from "@lotics/ui/agent_run";
13
14
 
15
+ // A real app fills the attachment from `useFileUpload` (the uploaded file's URL);
16
+ // this mock uses an inline placeholder so the example needs no network image.
17
+ const MOCK_PHOTO: DisplayFile = {
18
+ id: "att-carton",
19
+ filename: "carton.jpg",
20
+ mimeType: "image/jpeg",
21
+ url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
22
+ };
23
+
14
24
  // ─────────────────────────────────────────────────────────────────────────────
15
25
  // Template · AI design canvas (the carton "dieline" app) — the FLAGSHIP shape:
16
26
  // the page IS the design. The dieline fills the centre, a FLOATING composer sits
@@ -56,6 +66,7 @@ export function TplDieline() {
56
66
  const [pan, setPan] = useState({ x: 0, y: 0 });
57
67
  const [dragging, setDragging] = useState(false);
58
68
  const [prompt, setPrompt] = useState("");
69
+ const [attachment, setAttachment] = useState<DisplayFile | null>(null);
59
70
 
60
71
  // Drag-to-pan — like a design tool: grab anywhere on the canvas and move it.
61
72
  // The committed pan is read at grant via a ref (the responder is built once).
@@ -116,6 +127,7 @@ export function TplDieline() {
116
127
  const submitPrompt = (text: string) => {
117
128
  setPrompt("");
118
129
  if (!designReady) {
130
+ setAttachment(null); // the attached photo is consumed into the run
119
131
  startRecognize();
120
132
  return;
121
133
  }
@@ -136,6 +148,7 @@ export function TplDieline() {
136
148
 
137
149
  const newSession = () => {
138
150
  setRun(null);
151
+ setAttachment(null);
139
152
  setRevealed(0);
140
153
  setDesignReady(false);
141
154
  setDims({ L: 320, W: 230, H: 150 });
@@ -197,12 +210,16 @@ export function TplDieline() {
197
210
  {phase === "working" ? (
198
211
  <AgentProgress steps={liveSteps} state="streaming" />
199
212
  ) : (
200
- <PromptField
213
+ <Composer
201
214
  value={prompt}
202
215
  onChangeText={setPrompt}
203
- onSubmit={submitPrompt}
204
- onAttach={startRecognize}
205
- placeholder={designReady ? "Describe a change — “make it 5 mm taller”" : "Attach a photo of the carton to start…"}
216
+ onSend={submitPrompt}
217
+ sendDisabled={prompt.trim().length === 0 && !attachment}
218
+ placeholder={designReady ? "Describe a change — “make it 5 mm taller”" : "Attach a photo, add a note, then send…"}
219
+ actionsButton={
220
+ <Button icon="paperclip" color="secondary" accessibilityLabel="Attach a photo" onPress={() => setAttachment(MOCK_PHOTO)} />
221
+ }
222
+ files={attachment ? <FileThumbnail file={attachment} size={56} onRemove={() => setAttachment(null)} /> : null}
206
223
  />
207
224
  )}
208
225
  </View>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "4.6.0",
3
+ "version": "4.6.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",