Skip to content

feat(addressing): theorem:// client helper, console surfaces, desktop deep links - #81

Merged
Travis-Gilbert merged 2 commits into
mainfrom
claude/theorem-backend-cross-repo-dab169
Jul 19, 2026
Merged

feat(addressing): theorem:// client helper, console surfaces, desktop deep links#81
Travis-Gilbert merged 2 commits into
mainfrom
claude/theorem-backend-cross-repo-dab169

Conversation

@Travis-Gilbert

@Travis-Gilbert Travis-Gilbert commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Client half of DESIGN-THEOREM-URI. The engine half is a companion PR in the Theorem repo; this one is standalone-mergeable (the client grammar does not depend on the resolver shipping first).

What this does

Every object gets one address that works everywhere a person or an agent can point:

theorem://{tenant}/{kind}/{id}
theorem://{tenant}/{kind}/{id}?v={graph_version}
theorem://{tenant}/{kind}/{id}#span={selector}

The one client helperpackages/block-view/src/addressing.ts, exported as @commonplace/block-view/addressing. Mirrors the Rust emitter byte-for-byte (the encode set matches encodeURIComponent exactly), so an address minted on a phone and one minted in the engine compare equal as strings. parseTheoremUri returns a discriminated union instead of throwing, because paste handlers run on every paste.

It is hand-parsed on purpose: WHATWG new URL() treats theorem: as a non-special scheme and puts the tenant in host, not pathname — which is exactly how the previous mobile router silently dropped the tenant segment.

Mobile now re-exports the shared grammar and keeps only the expo-router mapping. Two corrections:

  • the fragment is #span={selector} per the grammar (it was a bare #<encoded string>)
  • routeForTheoremUri carries ?v= and #span onto the route instead of discarding them. A shared clipping that opens unspanned is a link that lies.

Console — the inspector footer renders the canonical address where the bare id used to, cards carry copy-address, mention chips and action-pack context carry the address, and the Composer and Search field accept pasted addresses. Paste offers rather than auto-inserts (matching the mobile Omnibar's "offer, never auto-insert" precedent), and refusals are shown rather than swallowed. Cross-tenant addresses are refused client-side with the tenant named; the engine still reconciles.

Desktoptauri-plugin-deep-link on Tauri v2 with the theorem scheme registered, plus an onOpenUrl acceptor in apps/web, which is the surface the Tauri window actually renders (frontendDist points there, not at apps/desktop/src).

Validation

Gate Result
console vitest 131/131
mobile vitest 47/47
tsc --noEmit (console, mobile, web) clean in my files
import-fence, register lint, motion inventory, icon gate clean

Notes for review

  • Deliberately not changed: packages/block-view is consumed as file:../../packages/block-view by console, web, and mobile, and pnpm materializes that as a copied snapshot, not a live link — so edits to packages/* reach no consumer until pnpm install re-runs. This bit during development. workspace:* would link it live but touches the Railway deploy path, so it is left as a deliberate decision rather than folded in here.
  • No e2e spec added. Playwright needs the dev server on 3010 and could not run here; shipping unrunnable oracles is worse than naming the gap. The assertions a follow-up should add are listed against the data-* hooks already in place (data-inspector-address, data-copy-address, data-address-resolve, data-address-refusal, data-composer-paste-offer).
  • Existing PNG baselines should be unaffected — the new affordances render only on full-size cards, or only once text carries an address. Worth a skim of the visual gate to confirm.
  • Survey clippings (spec section 3) are not included: that surface has no implementing code yet. The span grammar and its refusal path are complete, so it binds to a finished contract when it lands.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added canonical theorem:// addresses for objects, including optional version and text-span details.
    • Added copy-address controls to object cards and record inspectors.
    • Search now recognizes theorem addresses and can open matching objects.
    • Pasting an address offers options to add it as a chip or paste it as text, with clear refusal messages for invalid or cross-tenant addresses.
    • Desktop and web apps now support opening theorem links directly, including object deep-link routing.
  • Bug Fixes

    • Improved address validation and handling of malformed, unsupported, or out-of-range links.

… deep links

Client half of DESIGN-THEOREM-URI (engine half lands in Theorem).

- packages/block-view/src/addressing.ts: the one client helper the brief
  asks for, exported as @commonplace/block-view/addressing. Mirrors the
  Rust grammar byte-for-byte; parseTheoremUri returns a discriminated
  union rather than throwing, because paste handlers run on every paste.
  Hand-parsed on purpose: WHATWG URL treats theorem: as a non-special
  scheme and puts the tenant in host, which is how the old mobile router
  silently dropped it.
- mobile: re-exports the shared grammar and keeps only the expo-router
  mapping. Two corrections — the fragment is now #span={selector} per the
  grammar, and the router carries ?v= and #span onto the route instead of
  discarding them, since a shared clipping that opens unspanned is a link
  that lies.
- console: the inspector footer renders the address where the bare id
  was, cards carry copy-address, mention chips and action-pack context
  carry the address, and the Composer and Search field accept pasted
  addresses (offering, never auto-inserting) with refusals shown rather
  than swallowed.
- desktop: tauri-plugin-deep-link registered on Tauri v2 with the
  theorem scheme, plus an onOpenUrl acceptor in apps/web, which is the
  surface the Tauri window actually renders.

Tests: console 131/131, mobile 47/47, tsc clean in both, import-fence,
register, motion, and icon gates clean.
Copilot AI review requested due to automatic review settings July 19, 2026 15:13
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@ecc-tools

ecc-tools Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Travis-Gilbert, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b0feec0e-4238-404e-b375-d7cf33260c5e

📥 Commits

Reviewing files that changed from the base of the PR and between d9b939c and 21abf7d.

📒 Files selected for processing (1)
  • apps/web/src/components/commonplace/agency/WhyTrace.tsx
📝 Walkthrough

Walkthrough

The PR introduces a shared theorem:// addressing contract with span and refusal handling, propagates canonical addresses through console chips and messages, adds copy/paste and search support, and enables mobile, web, and desktop deep-link routing.

Changes

Theorem addressing

Layer / File(s) Summary
Canonical addressing contract
packages/block-view/src/addressing.ts, packages/block-view/src/index.ts, packages/block-view/package.json
Adds theorem URI types, rendering/parsing, span selectors, refusal results, extraction helpers, and package exports.
Mobile routing adapter and coverage
apps/mobile/src/addressing/*
Uses the shared contract for expo-router mapping and expands URI, span, extraction, and routing tests.
Console address propagation
apps/console/src/lib/*, apps/console/src/components/shell/ActionSheet.tsx, apps/console/AGENTS.md, apps/console/CLAUDE.md
Adds tenant-aware addresses to chips, action packs, staged references, and serialized mentions, with supporting invariants and ledger documentation.
Console copy, paste, and address search
apps/console/src/components/composer/Composer.tsx, apps/console/src/components/shell/SearchField.tsx, apps/console/src/components/shell/CopyAddressButton.tsx, apps/console/src/lib/use-copy.ts, apps/console/src/views/*
Adds clipboard copying, theorem-address paste offers and refusals, address-aware search resolution, and canonical address display in card and inspector views.
Web route and desktop deep-link handling
apps/web/src/app/(commonplace)/*, apps/web/src/components/commonplace/deeplink/*, apps/web/src/lib/*, apps/desktop/*, crates/commonplace-desktop-runtime/*
Adds an encoded object route, deep-link acceptance and resolution, Tauri bridge helpers, plugin configuration, permissions, and runtime registration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Clipboard
  participant Console
  participant Addressing
  participant WebShell
  Clipboard->>Console: paste theorem:// text
  Console->>Addressing: extract and parse address
  Addressing-->>Console: address or refusal
  Console->>WebShell: open encoded object route
  WebShell->>Addressing: parse route address
  Addressing-->>WebShell: resolved address
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: shared theorem:// addressing, console surface updates, and desktop deep-link support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/theorem-backend-cross-repo-dab169

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds client-side support for canonical theorem:// addressing across shared grammar utilities, console UI/UX entry points (copy/paste/mentions/search), and desktop deep linking via Tauri—so objects can be named uniformly across surfaces and opened from OS-level links.

Changes:

  • Introduces @commonplace/block-view/addressing with emit/parse helpers and span-selector support.
  • Wires theorem:// addresses into Console surfaces (inspector/card copy, action packs, composer paste offers, search resolve lane).
  • Enables Desktop deep links (Tauri deep-link plugin + web acceptor route and listener).

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds the Tauri deep-link plugin dependency to the lockfile.
packages/block-view/src/index.ts Re-exports the new addressing helper from the block-view package entrypoint.
packages/block-view/src/addressing.ts Implements the shared theorem:// grammar helpers (emit/parse/span/bounds/extract).
packages/block-view/package.json Exposes ./addressing as a package export path.
crates/commonplace-desktop-runtime/src/lib.rs Initializes deep-link plugin and registers scheme on Windows/Linux at runtime.
crates/commonplace-desktop-runtime/Cargo.toml Adds tauri-plugin-deep-link Rust dependency.
apps/web/src/lib/theorem-address-route.ts Maps parsed theorem addresses into the web shell’s object route via a single query param.
apps/web/src/lib/theorem-address-route.test.ts Tests round-tripping and refusal behavior for the web route mapping.
apps/web/src/lib/desktop.ts Adds dependency-free IPC wrappers for deep-link plugin (get_current, deep-link://new-url).
apps/web/src/components/commonplace/deeplink/TheoremDeepLink.tsx Accepts deep-link URLs (cold + warm) and routes to the addressed object, surfacing refusals via toast.
apps/web/src/components/commonplace/deeplink/ObjectAddressView.tsx Resolves the route query param into the existing drawer-based object surface (or refusal UI).
apps/web/src/app/(commonplace)/layout.tsx Mounts the deep-link acceptor component inside the shell layout.
apps/web/src/app/(commonplace)/commonplace/object/page.tsx Adds the static-export-safe acceptor route (/commonplace/object?address=...).
apps/mobile/src/addressing/theoremUriCore.ts Switches mobile to shared grammar; keeps only expo-router mapping and carries v/span through.
apps/mobile/src/addressing/theoremUriCore.test.ts Adds/updates tests to validate shared grammar parity and routing propagation.
apps/desktop/src-tauri/tauri.conf.json Registers the theorem scheme via the deep-link plugin config.
apps/desktop/src-tauri/capabilities/default.json Grants deep-link:default capability to the desktop app.
apps/desktop/package.json Adds the JS deep-link plugin dependency for the desktop bundle.
apps/console/src/views/RecordInspector.tsx Replaces bare id footer with canonical address display + copy button.
apps/console/src/views/CardView.tsx Adds copy-address affordance to full-size cards.
apps/console/src/lib/use-copy.ts Introduces a console-local clipboard hook with “copied/unavailable” state.
apps/console/src/lib/thread-store.ts Allows staged refs to serialize as canonical addresses when available.
apps/console/src/lib/shell-store.ts Adds address to staged object chips using tenant from the store.
apps/console/src/lib/object-address.ts Centralizes console minting of theorem addresses for objects/loose parts.
apps/console/src/lib/object-address.test.ts Tests console bindings (chip/pack/ref) against the shared grammar.
apps/console/src/lib/action-pack.ts Includes canonical address in action-pack context entries and equality checks.
apps/console/src/components/shell/SearchField.tsx Detects pasted addresses, refuses cross-tenant, and offers “Open …” resolve row.
apps/console/src/components/shell/icons.tsx Adds copy icon glyph for the new address affordance.
apps/console/src/components/shell/CopyAddressButton.tsx Implements a reusable copy-address button using the new clipboard hook.
apps/console/src/components/shell/ActionSheet.tsx Ensures auto-suggested chips carry addresses and staged refs preserve addresses.
apps/console/src/components/composer/Composer.tsx Makes mentions serialize to theorem addresses; adds “paste offer” flow for addresses.
apps/console/CLAUDE.md Documents the addressing + copy subsystem boundaries and single sources of truth.
apps/console/AGENTS.md Mirrors the addressing + copy subsystem documentation for agent contributors.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +162
case 'exact':
exact = decodeURIComponent(value);
break;
case 'prefix':
prefix = decodeURIComponent(value);
break;
case 'suffix':
suffix = decodeURIComponent(value);
break;
Comment on lines +289 to +298
return {
ok: true,
address: {
tenant: decodeURIComponent(tenant),
kind: decodeURIComponent(kind),
id: decodeURIComponent(id),
...(graphVersion !== undefined ? { graphVersion } : {}),
...(span !== undefined ? { span } : {}),
},
};

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/console/src/components/shell/SearchField.tsx (1)

40-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate address-detection/refusal logic with Composer.tsx's onPaste.

probeAddress re-implements the same extract-address, tenant-mismatch refusal, and "no theorem address" fallback logic already present in Composer.tsx's onPaste handler (same wording, same control flow). Two independent copies risk drifting as either surface evolves (e.g. refusal wording changes in one but not the other).

Consider extracting a shared detectTheoremAddress(raw, tenant) helper (e.g. alongside object-address.ts) that both Composer.tsx and SearchField.tsx call, so paste and search share one source of truth for detection and refusal messaging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/shell/SearchField.tsx` around lines 40 - 77,
Extract the shared address detection and tenant-refusal flow from probeAddress
and Composer.tsx’s onPaste into a shared detectTheoremAddress(raw, tenant)
helper near object-address utilities. Preserve the existing handling for mode
prefixes, embedded addresses, tenant mismatches, malformed tokens, and the “no
theorem address” fallback, then update both callers to use the helper and retain
their surface-specific behavior.
apps/console/src/components/composer/Composer.tsx (2)

249-300: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Paste offer/refusal panels aren't announced to screen readers.

CopyAddressButton (apps/console/src/components/shell/CopyAddressButton.tsx) uses aria-live="polite" for its status text; these new panels render pasteRefusal and the "Pasted address" offer without any live region, so a screen-reader user who just pasted won't be told the offer or refusal appeared.

♿ Proposed fix
-            {pasted ? (
-              <div
-                className="flex flex-wrap items-center gap-1 border-b border-ij-seam px-2 pt-2"
-                data-composer-paste-offer
-              >
+            {pasted ? (
+              <div
+                className="flex flex-wrap items-center gap-1 border-b border-ij-seam px-2 pt-2"
+                data-composer-paste-offer
+                role="status"
+                aria-live="polite"
+              >

Apply the same role="status" aria-live="polite" to the pasteRefusal container.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/composer/Composer.tsx` around lines 249 - 300,
Update the paste offer and refusal panel containers in the Composer render to
use the same role="status" and aria-live="polite" live-region behavior as
CopyAddressButton, ensuring both newly rendered “Pasted address” offers and
pasteRefusal messages are announced to screen readers.

127-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for the new paste-detection flow.

onPaste, acceptPastedChip, and keepPastedText implement several branches (tenant match, tenant mismatch, malformed address, no address) with real behavioral consequences (tenant-boundary refusal, chip staging), but no test file was included covering this logic.

Want me to draft a Vitest suite (mocking ClipboardEvent/useComposerRuntime) covering the tenant-match, tenant-mismatch, and malformed-address branches?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/composer/Composer.tsx` around lines 127 - 202,
The new paste-detection flow lacks tests for its behavioral branches. Add a
focused Vitest suite covering onPaste, acceptPastedChip, and keepPastedText,
including same-tenant address staging, cross-tenant refusal with plain-text
preservation, malformed or address-free input refusal messaging, canonical chip
creation, and keeping pasted text with the updated character count; mock the
clipboard event and composer runtime as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/console/src/components/composer/Composer.tsx`:
- Around line 192-202: Update keepPastedText to append pasted.raw with an
appropriate separator when existing composer text does not already provide one,
then truncate the resulting value to MAX_CHARACTERS before calling
composerRuntime.setText and setCharacterCount. Preserve the existing early
return and clear the pending paste after applying the bounded text.

In `@packages/block-view/src/addressing.ts`:
- Around line 309-323: Update extractTheoremAddress to remove surrounding
sentence punctuation from each whitespace-delimited token before calling
looksLikeTheoremAddress and parseTheoremUri. Preserve valid URI characters while
stripping trailing punctuation such as periods and closing parentheses, so
extracted ids never include prose noise.

---

Nitpick comments:
In `@apps/console/src/components/composer/Composer.tsx`:
- Around line 249-300: Update the paste offer and refusal panel containers in
the Composer render to use the same role="status" and aria-live="polite"
live-region behavior as CopyAddressButton, ensuring both newly rendered “Pasted
address” offers and pasteRefusal messages are announced to screen readers.
- Around line 127-202: The new paste-detection flow lacks tests for its
behavioral branches. Add a focused Vitest suite covering onPaste,
acceptPastedChip, and keepPastedText, including same-tenant address staging,
cross-tenant refusal with plain-text preservation, malformed or address-free
input refusal messaging, canonical chip creation, and keeping pasted text with
the updated character count; mock the clipboard event and composer runtime as
needed.

In `@apps/console/src/components/shell/SearchField.tsx`:
- Around line 40-77: Extract the shared address detection and tenant-refusal
flow from probeAddress and Composer.tsx’s onPaste into a shared
detectTheoremAddress(raw, tenant) helper near object-address utilities. Preserve
the existing handling for mode prefixes, embedded addresses, tenant mismatches,
malformed tokens, and the “no theorem address” fallback, then update both
callers to use the helper and retain their surface-specific behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9065f18-33c0-4292-bd6e-c19d82f4d6ea

📥 Commits

Reviewing files that changed from the base of the PR and between 1da1958 and d9b939c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • apps/console/AGENTS.md
  • apps/console/CLAUDE.md
  • apps/console/src/components/composer/Composer.tsx
  • apps/console/src/components/shell/ActionSheet.tsx
  • apps/console/src/components/shell/CopyAddressButton.tsx
  • apps/console/src/components/shell/SearchField.tsx
  • apps/console/src/components/shell/icons.tsx
  • apps/console/src/lib/action-pack.ts
  • apps/console/src/lib/object-address.test.ts
  • apps/console/src/lib/object-address.ts
  • apps/console/src/lib/shell-store.ts
  • apps/console/src/lib/thread-store.ts
  • apps/console/src/lib/use-copy.ts
  • apps/console/src/views/CardView.tsx
  • apps/console/src/views/RecordInspector.tsx
  • apps/desktop/package.json
  • apps/desktop/src-tauri/capabilities/default.json
  • apps/desktop/src-tauri/tauri.conf.json
  • apps/mobile/src/addressing/theoremUriCore.test.ts
  • apps/mobile/src/addressing/theoremUriCore.ts
  • apps/web/src/app/(commonplace)/commonplace/object/page.tsx
  • apps/web/src/app/(commonplace)/layout.tsx
  • apps/web/src/components/commonplace/deeplink/ObjectAddressView.tsx
  • apps/web/src/components/commonplace/deeplink/TheoremDeepLink.tsx
  • apps/web/src/lib/desktop.ts
  • apps/web/src/lib/theorem-address-route.test.ts
  • apps/web/src/lib/theorem-address-route.ts
  • crates/commonplace-desktop-runtime/Cargo.toml
  • crates/commonplace-desktop-runtime/src/lib.rs
  • packages/block-view/package.json
  • packages/block-view/src/addressing.ts
  • packages/block-view/src/index.ts

Comment on lines +192 to +202
const keepPastedText = useCallback(() => {
if (!pasted) return;
// The paste was held, so the caret offset is gone: the text appends. The
// person sees exactly what they copied, which is the point of the offer.
const next = `${composerRuntime.getState().text}${pasted.raw}`;
composerRuntime.setText(next);
// The runtime write bypasses the textarea's change event, so the counter
// is told directly rather than left reading a stale length.
setCharacterCount(next.length);
setPasted(null);
}, [composerRuntime, pasted]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

keepPastedText bypasses the composer's character limit and can run words together.

composerRuntime.setText(next) writes programmatically, so the textarea's maxLength={MAX_CHARACTERS} (line 306) never applies here; a long share-sheet clipboard blob can push the composer well past the 2000-character budget that every other input path enforces. The raw text is also appended with no separator, so it can run into the end of existing composer text.

🩹 Proposed fix
   const keepPastedText = useCallback(() => {
     if (!pasted) return;
     // The paste was held, so the caret offset is gone: the text appends. The
     // person sees exactly what they copied, which is the point of the offer.
-    const next = `${composerRuntime.getState().text}${pasted.raw}`;
+    const current = composerRuntime.getState().text;
+    const separator = current && !current.endsWith('\n') ? '\n' : '';
+    const next = `${current}${separator}${pasted.raw}`.slice(0, MAX_CHARACTERS);
     composerRuntime.setText(next);
     // The runtime write bypasses the textarea's change event, so the counter
     // is told directly rather than left reading a stale length.
     setCharacterCount(next.length);
     setPasted(null);
   }, [composerRuntime, pasted]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const keepPastedText = useCallback(() => {
if (!pasted) return;
// The paste was held, so the caret offset is gone: the text appends. The
// person sees exactly what they copied, which is the point of the offer.
const next = `${composerRuntime.getState().text}${pasted.raw}`;
composerRuntime.setText(next);
// The runtime write bypasses the textarea's change event, so the counter
// is told directly rather than left reading a stale length.
setCharacterCount(next.length);
setPasted(null);
}, [composerRuntime, pasted]);
const keepPastedText = useCallback(() => {
if (!pasted) return;
// The paste was held, so the caret offset is gone: the text appends. The
// person sees exactly what they copied, which is the point of the offer.
const current = composerRuntime.getState().text;
const separator = current && !current.endsWith('\n') ? '\n' : '';
const next = `${current}${separator}${pasted.raw}`.slice(0, MAX_CHARACTERS);
composerRuntime.setText(next);
// The runtime write bypasses the textarea's change event, so the counter
// is told directly rather than left reading a stale length.
setCharacterCount(next.length);
setPasted(null);
}, [composerRuntime, pasted]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/components/composer/Composer.tsx` around lines 192 - 202,
Update keepPastedText to append pasted.raw with an appropriate separator when
existing composer text does not already provide one, then truncate the resulting
value to MAX_CHARACTERS before calling composerRuntime.setText and
setCharacterCount. Preserve the existing early return and clear the pending
paste after applying the bounded text.

Comment on lines +309 to +323
/**
* Pull the first `theorem://` address out of arbitrary pasted text.
*
* Share sheets wrap an address in a title and a newline (`shareObject` in the
* mobile object drawer does exactly that), so accepting a paste means finding
* the address inside the noise rather than demanding a bare URI.
*/
export function extractTheoremAddress(text: string): TheoremAddress | undefined {
for (const token of text.split(/\s+/)) {
if (!looksLikeTheoremAddress(token)) continue;
const parsed = parseTheoremUri(token.trim());
if (parsed.ok) return parsed.address;
}
return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Trailing punctuation can leak into the extracted id.

extractTheoremAddress splits pasted text purely on whitespace (text.split(/\s+/)), so a token adjacent to sentence punctuation (e.g. theorem://t/doc/d1. or (theorem://t/doc/d1)) gets parsed with that punctuation folded into id, since most punctuation characters don't make parseTheoremUri refuse. This silently produces a wrong-but-plausible address from ordinary prose containing a link, rather than surfacing a refusal or stripping the noise.

🩹 Suggested fix: trim trailing punctuation before parsing
 export function extractTheoremAddress(text: string): TheoremAddress | undefined {
   for (const token of text.split(/\s+/)) {
     if (!looksLikeTheoremAddress(token)) continue;
-    const parsed = parseTheoremUri(token.trim());
+    const parsed = parseTheoremUri(token.trim().replace(/[).,;:!?'"]+$/, ''));
     if (parsed.ok) return parsed.address;
   }
   return undefined;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Pull the first `theorem://` address out of arbitrary pasted text.
*
* Share sheets wrap an address in a title and a newline (`shareObject` in the
* mobile object drawer does exactly that), so accepting a paste means finding
* the address inside the noise rather than demanding a bare URI.
*/
export function extractTheoremAddress(text: string): TheoremAddress | undefined {
for (const token of text.split(/\s+/)) {
if (!looksLikeTheoremAddress(token)) continue;
const parsed = parseTheoremUri(token.trim());
if (parsed.ok) return parsed.address;
}
return undefined;
}
/**
* Pull the first `theorem://` address out of arbitrary pasted text.
*
* Share sheets wrap an address in a title and a newline (`shareObject` in the
* mobile object drawer does exactly that), so accepting a paste means finding
* the address inside the noise rather than demanding a bare URI.
*/
export function extractTheoremAddress(text: string): TheoremAddress | undefined {
for (const token of text.split(/\s+/)) {
if (!looksLikeTheoremAddress(token)) continue;
const parsed = parseTheoremUri(token.trim().replace(/[).,;:!?'"]+$/, ''));
if (parsed.ok) return parsed.address;
}
return undefined;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/block-view/src/addressing.ts` around lines 309 - 323, Update
extractTheoremAddress to remove surrounding sentence punctuation from each
whitespace-delimited token before calling looksLikeTheoremAddress and
parseTheoremUri. Preserve valid URI characters while stripping trailing
punctuation such as periods and closing parentheses, so extracted ids never
include prose noise.

TraceGroup's onClick called onOpenEvidence?.(proposal, reference) but
proposal was never one of its props: it exists only in the outer WhyTrace
scope. `next build` failed with "Cannot find name 'proposal'", which took
the whole apps/web build down on main, not just on a branch.

The callback signature already declares (proposal, reference), and all
three call sites have proposal in scope, so threading it through is the
fix the code was written to expect.

Out of scope for the addressing work; included because it blocks CI here
and blocks the web build on main.
@ecc-tools

ecc-tools Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@Travis-Gilbert
Travis-Gilbert merged commit 330c318 into main Jul 19, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants