Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
50a5143
fix(assistant): refine queue, search and writing setup interactions
chaxus Oct 16, 2026
676a9c4
style(assistant): flatten surfaces and reduce nested controls
chaxus Oct 16, 2026
88034be
feat(assistant): add scoped sources and versioned artifact workspace
chaxus Oct 16, 2026
a38ba07
feat(connectors): add optional Google Drive authorization backend
chaxus Oct 16, 2026
eb8936e
fix(assistant): clarify recovery after reference changes
chaxus Oct 16, 2026
49f9e9e
feat(assistant): adapt workspace controls to offline availability
chaxus Oct 16, 2026
38fb318
fix(assistant): keep dictation feedback clear of drafts
chaxus Oct 16, 2026
60505ae
fix(assistant): make source and artifact recovery actionable
chaxus Oct 16, 2026
0ceeb99
fix(connectors): isolate canceled source operation ownership
chaxus Oct 16, 2026
cf76dbe
fix(connectors): finish disconnect before authorizing again
chaxus Oct 16, 2026
e29e56f
docs(assistant): define workspace craft acceptance contracts
chaxus Oct 16, 2026
ec28152
docs(connectors): record OAuth deployment acceptance steps
chaxus Oct 16, 2026
b56be65
fix(assistant): preserve draft and reading position across navigation
chaxus Oct 16, 2026
c32d423
fix(chat-ui): preserve active composition focus during navigation
chaxus Oct 16, 2026
7e059ea
docs(assistant): record navigation regression evidence
chaxus Oct 16, 2026
8fceaae
feat(assistant): stage contextual follow-ups and report save outcomes
chaxus Oct 16, 2026
d73de12
docs(assistant): record result action acceptance evidence
chaxus Oct 16, 2026
47f1c3a
feat(assistant): ground source discovery and captured citations
chaxus Oct 16, 2026
3f4dd3d
docs(assistant): record captured source acceptance evidence
chaxus Oct 16, 2026
c93dfa6
fix(assistant): narrow citation preview state before access
chaxus Oct 16, 2026
24e7d34
test(assistant): verify visited history restoration across sessions
chaxus Oct 16, 2026
529af94
fix(connectors): validate search controls without control regex
chaxus Oct 16, 2026
c94e07a
feat(assistant): read bounded Office and PDF references locally
chaxus Oct 16, 2026
4106116
docs(assistant): record standalone reference acceptance evidence
chaxus Oct 16, 2026
ed7035a
feat(assistant): review artifact revisions without losing drafts
chaxus Oct 16, 2026
81998c4
docs(assistant): record artifact revision acceptance evidence
chaxus Oct 16, 2026
25da17d
feat(assistant): add editable structured artifacts and offline exports
chaxus Oct 16, 2026
08c994f
fix(assistant): keep structured artifact save actions visible
chaxus Oct 16, 2026
abd3aff
docs(assistant): record structured artifact acceptance evidence
chaxus Oct 16, 2026
bbc0b6f
test(artifacts): retain sparse payload coverage without array ambiguity
chaxus Oct 16, 2026
2d0a2d4
feat(assistant): add precise location and owned change receipts
chaxus Oct 16, 2026
8cb05a8
test(assistant): retain native Stop cleanup contract
chaxus Oct 16, 2026
5175d21
fix(assistant): keep completed receipt actions compact
chaxus Oct 16, 2026
c9af418
test(assistant): assert capability-specific verified receipts
chaxus Oct 16, 2026
1fed06f
docs(assistant): record integrated workspace acceptance
chaxus Oct 16, 2026
1e6cae2
fix(assistant): settle Undo receipt status and focus
chaxus Oct 16, 2026
a82f5dc
docs(assistant): refresh final regression evidence
chaxus Oct 16, 2026
1a35114
fix(assistant): preserve source disclosures and trusted read evidence
chaxus Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions bin/offline-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

const ASSISTANT_ENTRY = 'lib/agent-plugin/agent-plugin.ts';
const GENERATED_ASSET = /^assets\/[A-Za-z0-9_.-]+\.(?:js|css|wasm|woff2)$/;
const GENERATED_ASSET = /^assets\/[A-Za-z0-9_.-]+\.(?:m?js|css|wasm|woff2)$/;

/** Follow app edges, optionally deferring lazy imports and attached runtime assets. */
function collectAssets(manifest, roots, excluded = new Set(), includeRuntime = true) {
Expand All @@ -15,7 +15,7 @@ function collectAssets(manifest, roots, excluded = new Set(), includeRuntime = t
if (!chunk) throw new Error(`Missing offline manifest dependency: ${key}`);
// Worker outputs can appear in a static UI chunk's assets without being a
// static import. Keep its styles and fonts, but defer executable payloads.
const attachedAssets = (chunk.assets ?? []).filter((asset) => includeRuntime || !/\.(?:js|wasm)$/.test(asset));
const attachedAssets = (chunk.assets ?? []).filter((asset) => includeRuntime || !/\.(?:m?js|wasm)$/.test(asset));
for (const asset of [chunk.file, ...(chunk.css ?? []), ...attachedAssets]) {
if (!GENERATED_ASSET.test(asset)) throw new Error(`Unsupported offline asset: ${asset}`);
assets.add(`/${asset}`);
Expand Down Expand Up @@ -45,10 +45,15 @@ export async function stampOfflineAssets(directory) {
// Worker outputs may be emitted outside the main manifest's dependency graph.
// They belong to the optional inference runtime, never to the first visit.
for (const name of await readdir(join(directory, 'assets'))) {
if (/\.(?:js|css|wasm)$/.test(name)) optional.add(`/assets/${name}`);
if (/\.(?:m?js|css|wasm)$/.test(name)) optional.add(`/assets/${name}`);
}
const fonts = (await readdir(join(directory, 'ran-fonts'))).filter((name) => name.endsWith('.woff2'));
for (const name of fonts) core.add(`/ran-fonts/${name}`);
// Vite-only previews retain the stable token URL; production builds may
// fingerprint it. Both shipped styles must work before any offline reload.
for (const name of await readdir(directory))
if (/^ran-tokens(?:\.[A-Za-z0-9_-]+)?\.css$/.test(name)) core.add(`/${name}`);
if ((await readdir(join(directory, 'ran-fonts'))).includes('fonts.css')) core.add('/ran-fonts/fonts.css');
const assets = [...core].sort();
const assistantAssets = [...optional].filter((asset) => !core.has(asset)).sort();
const path = join(directory, 'sw.js');
Expand Down
66 changes: 66 additions & 0 deletions docs/architecture/native-change-receipts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Native change receipts

The main editor remains the document canvas. Completed action previews collapse to
an outcome, the captured range/page label, text actions, and a closed change-detail
disclosure. `ui/task-receipt.ts` summarizes actual settled write results in one
collapsed row. Verified, sent/unverified, failed and cancelled results have separate
counts; reads, plans and model claims never increment the verified count. Every
write still has its own review. A summary grants no future tool permission.

## Location capabilities

| Editor | Supported location | Boundary |
| ---------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Word | Captured selection/cursor hint and original/result diff | Selection state is not a stable cross-edit anchor. No guessed duplicate-text search, hidden bookmarks, document formatting, or synthetic canvas highlight. |
| Excel | Native `wb.scrollToCell` for the explicit destination | Requires the captured API/document and active worksheet model identity. Does not change selection, cell values or history. Cursor changes alone do not expire a completed location. |
| PowerPoint | Native `goToPage` for the captured slide object | Re-resolves its current index after reorder. Unknown page, deletion, replacement or document replacement refuses location. Duplicate text never selects a substitute slide. |
| PDF | Existing page/range hint and review | No unsupported body anchor is invented. |

Post-write location uses the verified continuation target. Pre-write selection and
history freshness still protect execution; they do not unnecessarily invalidate a
completed receipt when a user moves their selection. Location failure appears next
to its action and does not overwrite the verified application outcome.

## One owned Word Undo

`word-paste.ts` mints a volatile completed receipt only after
`beginWordPasteHistory.commit()` and native HTML paste cleanup. The bundled
`asc_PasteData` opens `historydescription_Document_PasteHotKey` (159). The receipt
requires that exact known constant/description, a nonempty single owned point,
`canEdit()`, no long action, no group action, known single-user collaboration state,
no global lock, no read-only mode, and register/unregister callback capability.
GroupPoints, typing/composite points, unknown native writes, other editors and
collaborative sessions have no custom Undo capability.

`completed-paste.ts` captures the history/Points array identity, exact current tail
index, every prefix/tail point, every item identity, and descriptions. Immediately
before Undo, a synchronous read checks that proof plus current document identity
and editor readiness. It then consumes the receipt before invoking **one** native
`api.Undo()` and verifies the preceding index and retained redo tail. A failed native
verification is reported as uncertain; it is never automatically retried. The
receipt never truncates native history or calls an arbitrary Undo loop.

The real `asc_onUndoRedo` event permanently invalidates a receipt, including a user
Undo/Redo round trip that restores the same tail. Later edits are caught by the
exact tail check. A newly started paste disposes the previous active receipt for
that API. A document replacement observer, preview replacement, conversation clear,
page hide and panel teardown remove only this receipt's own callback. Disposal
releases its prefix/item snapshot copies. Other SDK callbacks remain registered.

Tool results stay serializable: a WeakMap associates a result with a receipt until
`DocumentToolAction` or `ReviewedAction` claims it. Native objects/functions are not
persisted in conversations or sent to a model. Failed/unverified execution disposes
its receipt. Unsupported/expired Undo explains that editor Undo may include later
user edits. A location/Undo refusal never retroactively changes a verified apply
into a failed apply.

## Regression evidence

- Unit: exact foreign point/item/prefix/description replacement, appended history,
Undo/Redo invalidation, expiry/disposal, one-shot consumption and uncertain Undo.
- Native adapter: unavailable callbacks, grouping/collaboration/read-only/busy,
retained redo point, 50 writes with a constant owned callback count, document
replacement and preservation of foreign callbacks.
- Real editor browser: Word receipt Undo and native Redo; refusal after a later
user edit or native Undo/Redo; Excel locate after changing selection; PowerPoint
locate after native slide reorder. No vendor SDK files are modified.
17 changes: 17 additions & 0 deletions docs/architecture/structured-artifacts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Structured artifact boundary

Artifacts remain explicit local saves, independent of conversation retention. Ordinary chat saves remain text. **Import structured result** accepts an explicit JSON envelope from a result the user chooses to import:

```json
{ "schemaVersion": 1, "payload": { "kind": "table", "columns": ["Name"], "rows": [["Value"]] } }
```

A slides payload uses `{"kind":"slides","slides":[{"title":"Title","body":"Body"}]}`. A text payload uses `{"kind":"text","text":"Content"}`. Unknown schemas, fields, non-string values, sparse arrays and inconsistent row widths are rejected. Tables allow at most 50 columns, 500 rows and 10,000 cells. Slides allow 50 slides. Each structured field allows 16 KiB UTF-8; serialized payloads allow 128 KiB. Legacy plain-text versions retain their full 128 KiB capacity. Artifact/version counts remain 50/20; no saved history is silently pruned.

`artifacts/payload.ts` owns validation and canonical text. The store validates before transactions and validates records again on read, stores copies, preserves provenance and atomically checks expected versions. `ui/artifact-presentation.ts` owns a ten-row editable table page or one editable slide. A collapsed JSON source disclosure retains the existing text draft capture boundary. Editing a field synchronizes canonical source without replacing the active input or caret. Malformed source remains editable; saving and exporting are blocked until repaired.

Text refinements preserve existing selected-range behavior. Structured refinements target the whole payload, require the same kind and bounded schema, validate before showing an actionable proposal and validate again before acceptance. Acceptance changes the draft only; saving a version remains explicit. Document/source/provider freshness guards continue to apply.

TXT, Markdown, CSV and JSON exports are explicit browser Blob downloads and work offline without a model or network call. CSV is available for tables only, quotes every value and neutralizes formula-like prefixes, including control/whitespace prefixes, without changing stored data or JSON. Previews only use text and native form fields, never generated HTML or JavaScript.

Structured data is not directly applied to Office documents: there is no verified atomic table-range write contract yet, and artifact fields can exceed native slide limits. The interface explains the export path instead of silently truncating, emitting cell-by-cell writes, or exporting the unrelated current document. Existing text-to-Word application still uses fresh document review.
4 changes: 2 additions & 2 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ Keep this functional icon separate from legal attribution and from page headers.

Browser favicons use `document-light.svg` / `document-dark.svg` and their 32 px
PNG fallbacks. Both share the abstract mark geometry. The light variant uses the
light background / foreground tokens; the dark variant resolves
`--ran-background-100` to #000000, `--ran-gray-1000` to #ededed. Regenerate these PNGs with the same command above.
foreground token on a transparent canvas; the dark variant uses
`--ran-gray-1000` (#ededed) on the same transparent canvas. Browser tab icons must not inherit the filled PWA installation tile. Regenerate these PNGs with the same command above.

`public/theme-presentation.js` follows the effective `data-ran-theme`, then the
stored choice, then the system preference. It updates the favicon, native
Expand Down
37 changes: 37 additions & 0 deletions docs/developer/assistant-workspace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Assistant workspace owners

The panel composes existing inference and document-review lifecycles with two independent features. A model service is an execution destination, a connector is a reference source, and an artifact is an explicitly saved result. None creates a site account.

## Sources

`connectors/reference-sources.ts` owns immutable reference snapshots, per-conversation attachment sets and a read-only MCP client. Local references support `.txt`, `.md`, `.csv`, `.tsv` and `.json` up to 256 KiB, plus standalone DOCX, XLSX, PPTX and text-bearing PDF readers with 20 MiB input and 256 KiB extracted-text limits. These local readers do not replace or mutate the open document. OOXML extraction bounds archives to 2,000 entries, 32 MiB cumulative inflation and 100,000 XML elements, with 10,000 paragraphs or 10,000 cells across 20 sheets; PDF extraction allows 100 pages. All extraction has a 15-second deadline; scanned images require OCR, which is not provided. See [reference limits](../reference.md) for format and offline requirements. Up to five attachments are selected explicitly. Attachments remain in memory and are not silently restored from disk.

The MCP client supports the 2025-11-25 Streamable HTTP resource protocol with JSON and SSE responses. It lists up to 100 resources per explicit page and reads selected text resources. Load more follows only the currently returned cursor, with repeated-cursor protection, at most 10 pages and 500 unique resources; no page is prefetched. It does not invoke tools, run server instructions, implement stdio or handle provider OAuth. Use an explicitly configured HTTPS endpoint or HTTP loopback endpoint, with browser CORS access. An optional bearer token remains in memory. This is separate from the editor's outbound WebMCP tool exposure.

`ui/sources-view.ts` owns source management and authorization completion presentation. Authorization failures restore connection controls and clear expired discovery results without silently dropping attached snapshots or retrying. Attachment capacity errors identify the five-source limit. Source reads have byte limits and deadlines; epoch checks prevent late reads from entering another conversation or reattaching after disconnect. `panel.ts` captures selected references at enqueue, guards them before execution and continuation, and cancels execution when its attachments are invalidated. Adding another attachment cannot change an already queued snapshot.

`connectors/reference-search.ts` owns literal local filtering and query-centered excerpt windows. Source disclosures show up to 8,000 characters, highlight the actual match and label partial previews. Expanding a source or a captured citation never fetches content or starts inference. Google filename search runs only on explicit Browse; changing the query cancels the previous presentation owner and resets continuation. Drive discovery deduplicates up to 300 files across 10 explicit pages.

`ui/request-context.ts` creates bounded JSON excerpts with controlled ids S1–S5, source titles, connector identity and truncation flags. Host locators stay outside the model payload. It returns the exact serialized evidence, and native/ordinary-chat routes publish their final serialization before streaming. The runtime reports any additional clipping before each inference. `ui/source-evidence.ts` reconciles this with the immutable captured snapshot. `ui/source-citations.ts` decorates only matching literal citations using text-only captured excerpts; unknown ids remain plain text and original Copy/Save content is unchanged.

Each inference response can carry a host-minted stable `hostPresentation.id` with bounded presentation metadata, attached directly to that response rather than matched by prose or history index. The runtime strips the annotation before all provider calls. Conversation opt-in is still required for persistence; removing a conversation removes its annotations. Only the bounded excerpts actually sent are retained, never the full attachment. Restored legacy responses without annotations do not invent citations. Instruction and existing request-envelope headroom are reserved. Native requests, conversational requests, document drafting and dedicated writing routes receive quoted reference data through their existing owners. Remote content never adds tool definitions or bypasses host review.

Google Drive is optional and uses the [server connector](google-drive-connector.md). The service worker bypasses all `/api/connectors/` routes, including OAuth callback navigations, so authenticated responses cannot become offline assets. Cloudflare's `_routes.json` restricts function invocation to the Drive API path. Static deployments retain local/MCP references and explain unavailable cloud routes.

## Artifacts

`artifacts/store.ts` owns a separate IndexedDB database with artifact bodies and metadata-only list summaries. Saving is explicit; conversation-history opt-in does not implicitly save results. A store holds at most 50 artifacts, 20 versions each and 128 KiB UTF-8 per version. Limits reject with a visible recovery message; existing versions are never silently pruned.

Updates check the expected version inside the same read-write transaction that appends the new version. Concurrent editors cannot overwrite one another. Selecting an older version and saving creates a new version. Deletion requires an explicit UI confirmation. Failed saves preserve editable drafts. Capacity errors distinguish artifact count, text size and version history. At 20 versions, an explicit save-as-new action preserves the draft and source provenance in a separate artifact without modifying existing history.

`ui/artifacts-view.ts` owns local text editing, version selection and conflict presentation. The generic chat package provides an optional host save action with opaque, render-time provenance. It does not know IndexedDB or connectors. Source titles and conversation identity are captured with live results; old restored results with no retained source metadata do not invent source provenance.

`artifacts/edit-proposal.ts` owns immutable draft identity, title/body/revision freshness and native UTF-16 selection offsets. A collapsed selection means the complete artifact; selecting a range sends and replaces only that range. `ui/artifact-workflow.ts` owns a single disclosed instruction, bounded streaming preview and explicit diff acceptance. Accepting updates the editable draft only; Save version remains separate. Default previews show at most 8,000 characters; expanding the complete diff is explicit. Stop, rejection and failures keep the instruction and draft. A quiet scope line distinguishes selected text from the entire artifact before submission. Queue recovery routes instructions back to artifact refinement, never through the Office chat composer. If a restored selection belonged to an older body, a fresh selection or explicit entire-artifact choice is required; old offsets are not reused.

`ui/artifact-request.ts` dispatches through the panel's existing global request queue before any Office capture, classifier or controller. It uses only the selected, already prepared provider with no tool definitions or automatic connection/download/fallback. Captured attached references use the existing bounded request serializer. Document and artifact requests cannot run models concurrently; cancelling a queued artifact removes only its entry, and stopping active inference holds the global lease until the provider physically settles. Draft/title/version/navigation, source, conversation and provider changes invalidate a proposal. Persisted versions and current draft/context guards are checked before and after inference and again after the asynchronous acceptance read. A concurrent save or version cap offers explicit Save as new, preserving the current draft/title and original creation provenance without overwriting the newer artifact.

Applying an artifact captures a fresh Word target and creates the existing `ActionPreview`; nothing writes until the user confirms that preview. Queue application leases, target freshness, host verification and native Undo remain the existing owners. The artifact editor never executes generated code or renders arbitrary HTML.

## Offline availability

Offline mode hides Google Drive authorization and browsing, the downloadable model catalog and remote model URLs. It retains attached reference snapshots, local file attachment, saved artifact editing and versions, cached model management, local GGUF files, and local or LAN services/MCP. Existing cloud configuration remains inspectable. Connectivity restoration only restores controls; it never authorizes, downloads, reconnects or resends automatically. Browser connectivity is a presentation signal; actual request failures still receive actionable recovery messages.
Loading
Loading