Repository navigation
feat: add Ink conversation picker and shared conversation defaults - #285
Open
Liangwei Wang (LiangweiOIO) wants to merge 2 commits into
Open
Liangwei Wang (LiangweiOIO) wants to merge 2 commits into
Liangwei Wang (LiangweiOIO) wants to merge 2 commits into
Conversation
Reuse existing Agent sessions by default, persist per-Space accepted conversation recency, and keep lassoed Agent nodes out of Ink source targeting. Preserve cancelled Lasso drafts and guard conversation storage across Space renames and Workspace activation. Extend the OCR deadline to three seconds and add production regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: LiangweiOIO <64085199+LiangweiOIO@users.noreply.github.com>
Copilot started reviewing on behalf of
Liangwei Wang (LiangweiOIO)
October 8, 2026 10:02
View session
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The endpoint has a Workspace race, recovery does not converge, and picker focus behavior needs correction.
4 open findings
What changed in this PR
Adds an Ink conversation destination picker and server-owned recent-conversation defaults shared across clients.
Changes:
- Adds explicit New/Continue destination selection with resilient Lasso state.
- Persists accepted conversation recency with Workspace-aware storage.
- Extends tests, documentation, responsive UI, and OCR timeout.
| File | Description |
|---|---|
packages/shared/src/types/api/canvas.ts |
Defines recent-conversation API schemas. |
docs/proposals/ink-lasso-question-flow.md |
Documents picker and continuity design. |
docs/architecture/web-architecture.md |
Records picker UI conventions. |
docs/architecture/sketch-node.md |
Documents Ink destination behavior. |
docs/architecture/canvas-storage.md |
Describes recency persistence. |
docs/architecture/canvas-input-interactions.md |
Documents selection-independent targeting. |
docs/architecture/api-design.md |
Specifies the new endpoint. |
docs/architecture/agent-context.md |
Documents context exclusion and OCR. |
apps/web/src/store/gesturePreviewStore.ts |
Adds committed-Lasso identity. |
apps/web/src/i18n/resources/zh-CN/common.json |
Adds Chinese picker strings. |
apps/web/src/i18n/resources/en/common.json |
Adds English picker strings. |
apps/web/src/hooks/useCanvasLasso.gesture.test.tsx |
Tests Lasso identity lifecycle. |
apps/web/src/hooks/areaSelectionSession.ts |
Commits and restores Lasso identity. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/useInkConversationDestination.ts |
Resolves destination defaults. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/StrokeSelectionToolbar.tsx |
Integrates picker and submission routing. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/StrokeSelectionToolbar.test.tsx |
Tests destination and retry behavior. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/NodeToolbar.css |
Adds responsive picker layout. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/inkQuestionSubmission.ts |
Separates sources from destinations. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/inkQuestionSubmission.test.ts |
Tests explicit target derivation. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/InkAgentDestinationPicker.tsx |
Implements the destination menu. |
apps/web/src/components/Panels/Canvas/FloatingToolbars/InkAgentDestinationPicker.test.tsx |
Tests menu interaction and localization. |
apps/web/src/api/canvas.ts |
Adds recent-conversation fetch helper. |
apps/web/src/api/_routes.ts |
Adds endpoint route generation. |
apps/web/e2e/ink-toolbar.spec.ts |
Covers picker browser workflows. |
apps/web/e2e/fixtures/ink-toolbar.tsx |
Provides the browser-test fixture. |
apps/server/src/modules/workspace/paths.ts |
Rebinds conversation namespaces safely. |
apps/server/src/modules/canvas/canvas.route.ts |
Exposes the recency endpoint. |
apps/server/src/modules/agent/substrate-store.ts |
Adds strict document reads. |
apps/server/src/modules/agent/recent-conversation.ts |
Implements recency persistence and recovery. |
apps/server/src/modules/agent/recent-conversation.test.ts |
Tests storage and Workspace isolation. |
apps/server/src/modules/agent/conversation/ink-ocr.ts |
Raises OCR deadline to three seconds. |
apps/server/src/modules/agent/conversation/ink-ocr.test.ts |
Updates deadline coverage. |
apps/server/src/modules/agent/agenetes/conversation-stores.ts |
Adds leases, rebinding, and recency hooks. |
apps/server/src/modules/agent/agenetes/conversation-stores.test.ts |
Updates namespace test setup. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Comment on lines
+598
to
+602
| if (!(await space(canvasId).read())) { | ||
| return reply.code(404).send({ message: 'Canvas not found' }); | ||
| } | ||
| return reply.send( | ||
| await readRecentCanvasConversation(canvasId, conversationEventLogStore), |
Comment on lines
+129
to
+135
| const index = await readIndex(handle); | ||
| if (!index.pending) return index; | ||
| return { | ||
| conversation: (await findAcceptedStart(namespace, index.pending, events)) | ||
| ? index.pending.conversation | ||
| : index.conversation, | ||
| }; |
Comment on lines
+149
to
+152
| onMouseDown={(event) => { | ||
| event.preventDefault(); | ||
| event.stopPropagation(); | ||
| }} |
| **Cross-region split / merge (drag).** A **pure** stroke selection (no whole node in the lasso) dropped onto **blank canvas** splits into a brand-new sketch region; dropped onto **another** sketch region it merges into it. On commit [useSketchStrokeMove.ts](../../apps/web/src/hooks/useSketchStrokeMove.ts) hit-tests the drop point in absolute flow (excluding the source regions, topmost wins) and, for a cross-region drop, dispatches the `MOVE_SKETCH_STROKES_TO_REGION` UI intent instead of the in-node translate. [resolveMoveSketchStrokesToRegion.ts](../../apps/web/src/handler/canvasCommand/resolvers/resolveMoveSketchStrokesToRegion.ts) resolves the destination parent frame from the drop point (`resolveFrameAtPoint`) and calls [buildSketchStrokeTransferCommands](../../apps/web/src/components/Nodes/sketch/sketchMerge.ts), which works in **absolute flow** (`getAbsolutePosition`) so transfers across frames stay correct and degrade to the plain in-node math when the parent is unchanged; the source side reuses the extracted pure core `computeEraseCommands` (reflow the remainder, or delete the node when emptied). Splitting **into a frame** shows the same grow-to-fit accept-preview a whole-node drag gets (`computeFrameFit` + `setFrameFitPreviews`); merge / in-place / blank-top-level drops show none. The whole transfer is **one undo entry** — `canvasStore.moveSketchStrokesToRegion` brackets it with `beginNodeDataGesture` / `endNodeDataGesture` (with an empty-op `rollbackGestureSnapshot` guard). Deferred: auto-contact ("bridging") merge, edge rewiring when a source is emptied, and OCR-rerun on split (see the [sketch-region-redesign proposal](../proposals/sketch-region-redesign.md)). | ||
|
|
||
| **Toolbar arbitration.** A [StrokeSelectionToolbar](../../apps/web/src/components/Panels/Canvas/FloatingToolbars/StrokeSelectionToolbar.tsx) floats above the stroke selection: on a **pure, single-color** selection it shows color + size controls (reusing [SketchControls](../../apps/web/src/components/Nodes/sketch/SketchControls.tsx), applied only to the selected strokes — the brush preset is untouched). Each color or size tick builds one `MERGE_NODE_DATA` command containing every affected sketch patch, matching the multi-node accent path's atomic update and undo semantics; the size slider's gesture bracket folds all ticks into one undo entry. A **Delete** action (touch only — desktop uses the keyboard) reuses the eraser's `buildEraseCommands` (subset removal → bbox reflow, or node delete when empty). Delete removes the **whole selection** — strokes plus any whole nodes the same lasso caught — as **one undo entry**: the node delete takes its snapshot + intent trace, then the stroke erase folds into that same entry via [commitStrokeCommands](../../apps/web/src/components/Nodes/sketch/sketchMerge.ts) (`foldIntoOpenGesture`), mirroring the mixed stroke-move gesture. **Delete / Backspace** triggers the same combined delete (guarded against text inputs); the canonical keyboard handler in [useCanvasShortcuts.ts](../../apps/web/src/hooks/shortcuts/useCanvasShortcuts.ts) **skips its own node deletion while a stroke selection is active** so the keypress never pushes a second snapshot. To guarantee at most one floating toolbar, the node toolbars (single-select in [NodeWrapper.tsx](../../apps/web/src/components/Nodes/NodeWrapper.tsx) and MultiSelect) hide whenever a stroke selection exists. Pure and mixed Ink selections retain the source count and submit action; an adjacent compact target hint shows `New · <recent Agent name>` when the Lasso contains no Question/Agent Node, the effective bound Agent name when it contains one valid target, or a blocked state for multiple or invalid targets. New Ink Questions refresh the Profile catalogue and apply the browser-local recent conversational Agent before creation, falling back to the first selectable external Profile when that cache is absent or stale and using Built-In operate or external ask mode; existing targets keep their binding. The target hint is metadata rather than a source and never changes the source count. Rendering a stroke selection to PNG and sending it to the agent is covered in §4.1. Optional submission-time OCR uses only the explicitly selected strokes as transient Agent evidence and does not persist recognized text into the Sketch; eager/background region OCR remains deferred (see [sketch-region-redesign proposal](../proposals/sketch-region-redesign.md) Stage 3). | ||
| **Toolbar arbitration.** A [StrokeSelectionToolbar](../../apps/web/src/components/Panels/Canvas/FloatingToolbars/StrokeSelectionToolbar.tsx) floats above the stroke selection: on a **pure, single-color** selection it shows color + size controls (reusing [SketchControls](../../apps/web/src/components/Nodes/sketch/SketchControls.tsx), applied only to the selected strokes — the brush preset is untouched). Each color or size tick builds one `MERGE_NODE_DATA` command containing every affected sketch patch, matching the multi-node accent path's atomic update and undo semantics; the size slider's gesture bracket folds all ticks into one undo entry. A **Delete** action (touch only — desktop uses the keyboard) reuses the eraser's `buildEraseCommands` (subset removal → bbox reflow, or node delete when empty). Delete removes the **whole selection** — strokes plus any whole nodes the same lasso caught — as **one undo entry**: the node delete takes its snapshot + intent trace, then the stroke erase folds into that same entry via [commitStrokeCommands](../../apps/web/src/components/Nodes/sketch/sketchMerge.ts) (`foldIntoOpenGesture`), mirroring the mixed stroke-move gesture. **Delete / Backspace** triggers the same combined delete (guarded against text inputs); the canonical keyboard handler in [useCanvasShortcuts.ts](../../apps/web/src/hooks/shortcuts/useCanvasShortcuts.ts) **skips its own node deletion while a stroke selection is active** so the keypress never pushes a second snapshot. To guarantee at most one floating toolbar, the node toolbars (single-select in [NodeWrapper.tsx](../../apps/web/src/components/Nodes/NodeWrapper.tsx) and MultiSelect) hide whenever a stroke selection exists. Pure and mixed Ink selections retain the source count, explicit conversation picker, and submit action. The picker is not a source; lassoed Agent Nodes never determine its destination or contribute source counts. Conversation-first defaults, explicit New, and existing-thread continuation are described in §4.1. Rendering a stroke selection to PNG and sending it to the agent is also covered there. Optional submission-time OCR uses only the explicitly selected strokes as transient Agent evidence and does not persist recognized text into the Sketch; eager/background region OCR remains deferred (see [sketch-region-redesign proposal](../proposals/sketch-region-redesign.md) Stage 3). |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Scope
Review fixes
Addressed all three findings from the pre-PR code review:
Validation
pnpm typecheck,pnpm format,pnpm check:i18n, andgit diff --check.pnpm lint:fix --ignore-pattern tmp/dev-ipad.mjs: zero errors; existing warnings remain. The unmodified command encounters fiveno-undeferrors only in that git-ignored local helper, which is not part of this PR.PostgreSQL integration was not exercised. The persistence coordination retains the existing single-process lease/mutex guarantees rather than introducing cross-process transactions.