Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions AGENT_COORDINATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ Server Error) for an unknown slug.

## Active claims (who is editing what RIGHT NOW)

- **2026-07-29 · Codex `/root` →** `apps/web/src/features/entities/components/notebook/EntityNotebookLive.tsx`,
its mount/tests, and `docs/design/note-surface/*` · complete the actual Live notebook
Cmd/Ctrl+E capture path and rebuild the Mobbin/Mew/Roam evidence chain · branch
`codex/note-surface-complete` / PR #601. No backend or Convex deploy.

> **STANDARD-TREE MIGRATION (2026-07-19, feat/standard-tree-migration): repo paths moved.**
> `convex/` → `backend/convex/` (convex.json `functions` added; function identifiers unchanged),
> `src/` + `index.html` → `apps/web/` (`@convex` alias replaces relative `../convex` imports),
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This directory contains append-only per-surface changelog lanes. Each lane recor

### Components

- [`components/entity-notebook-surface.md`](components/entity-notebook-surface.md) - Entity notebook composition and authority propagation.
- [`components/entity-notebook-live.md`](components/entity-notebook-live.md) - Live block stream, capture shortcut, and fail-closed editing.
- [`components/fast-agent-panel.md`](components/fast-agent-panel.md) — FastAgentPanel AI Elements cutovers and preserved behavior seams.

### Integrations
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG/components/entity-notebook-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Entity notebook Live surface

Append-only lane for the block-stream editor, its read/edit transition, capture
shortcut, and fail-closed notebook authority behavior. Newest entries first.

## 2026-07-29 - Arm desktop capture from read mode

Make Cmd/Ctrl+E enter edit mode and focus the first editable Live block. Empty
notebooks create exactly one block, while active text inputs, repeated shortcuts,
shared routes, and notebooks containing only read-only blocks remain protected.

**PR / canonical main commit**: PENDING #601 MAIN SHA / FINAL QA.

**Evidence state**:
- Source: pending on PR #601.
- Checks: TypeScript passed; the focused EntityNotebookSurface and EntityNotebookLive scenario pack passed 15/15; production build passed; the NodeKit reference corpus gate passed 10/10 records with 21 facts and 53 citations.
- Visual proof: pending authenticated Chrome capture; failing-before and passing-after behavior receipts are in `evidence/note-surface-live-capture/`.
- Preview: pending exact-head deployment after this update.
- Production live: not recorded.

**Author**: Homen Shum + Codex.
**Touches**: [`entity-notebook-surface.md`](entity-notebook-surface.md).
24 changes: 24 additions & 0 deletions CHANGELOG/components/entity-notebook-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Entity notebook surface

Append-only lane for the entity notebook composition boundary that selects the
fallback or Live notebook and passes workspace authority into the mounted
surface. Newest entries first.

## 2026-07-29 - Preserve write authority across read mode

Pass the notebook's underlying write authority into the Live surface instead of
discarding it when the route renders in read mode. The Live component now owns
the temporary read-only mask, allowing an authorized reader to use the capture
shortcut without granting write access to shared or read-only notebooks.

**PR / canonical main commit**: PENDING #601 MAIN SHA / FINAL QA.

**Evidence state**:
- Source: pending on PR #601.
- Checks: TypeScript passed; the focused EntityNotebookSurface and EntityNotebookLive scenario pack passed 15/15; production build passed.
- Visual proof: pending authenticated Chrome capture; component focus behavior is recorded in `evidence/note-surface-live-capture/after.txt`.
- Preview: pending exact-head deployment after this update.
- Production live: not recorded.

**Author**: Homen Shum + Codex.
**Touches**: [`entity-notebook-live.md`](entity-notebook-live.md).
Original file line number Diff line number Diff line change
Expand Up @@ -508,12 +508,16 @@ export const EntityNoteEditor = forwardRef<EntityNoteEditorHandle, EntityNoteEdi
<div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="inline-flex rounded-full border border-[rgba(15,23,42,0.08)] bg-white/80 p-1 dark:border-white/10 dark:bg-white/[0.04]">
{/* rule-single-accent-inline obs-mew-tokens/f1 + obs-mew-stream/f2: the mode
toggle is chrome, so its selected state is achromatic — the accent stays
reserved for inline meaning carriers (citations, links, tags). This also
removes the hardcoded #d97757 literals that bypassed --accent-primary. */}
<button
type="button"
onClick={() => setMode("rich")}
className={`rounded-full px-3 py-1.5 text-xs font-medium transition ${
mode === "rich"
? "bg-[#d97757] text-white"
? "bg-[rgba(15,23,42,0.85)] text-white dark:bg-white/[0.14] dark:text-white"
: "text-content-muted hover:text-content"
}`}
>
Expand All @@ -524,7 +528,7 @@ export const EntityNoteEditor = forwardRef<EntityNoteEditorHandle, EntityNoteEdi
onClick={() => setMode("markdown")}
className={`rounded-full px-3 py-1.5 text-xs font-medium transition ${
mode === "markdown"
? "bg-[#d97757] text-white"
? "bg-[rgba(15,23,42,0.85)] text-white dark:bg-white/[0.14] dark:text-white"
: "text-content-muted hover:text-content"
}`}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ vi.mock("@/features/entities/components/EntityNotebookView", () => ({
),
}));

vi.mock("@/features/entities/components/notebook/EntityNotebookLiveMount", () => ({
EntityNotebookLiveMount: ({
entitySlug,
canEdit,
latestHumanEditorOwnerKey,
}: {
entitySlug: string;
canEdit: boolean;
latestHumanEditorOwnerKey?: string | null;
}) => (
<div data-testid="mock-live-notebook">
live:{entitySlug}:{String(canEdit)}:{latestHumanEditorOwnerKey ?? "none"}
</div>
),
}));
vi.mock(
"@/features/entities/components/notebook/EntityNotebookLiveMount",
() => ({
EntityNotebookLiveMount: ({
entitySlug,
canEdit,
latestHumanEditorOwnerKey,
}: {
entitySlug: string;
canEdit: boolean;
latestHumanEditorOwnerKey?: string | null;
}) => (
<div data-testid="mock-live-notebook">
live:{entitySlug}:{String(canEdit)}:
{latestHumanEditorOwnerKey ?? "none"}
</div>
),
}),
);

describe("EntityNotebookSurface", () => {
const baseProps = {
Expand Down Expand Up @@ -87,7 +91,7 @@ describe("EntityNotebookSurface", () => {
expect(screen.queryByTestId("mock-notebook-view")).not.toBeInTheDocument();
});

it("turns the live notebook into a read-only report surface in read mode", async () => {
it("preserves underlying write authority while the live notebook renders in read mode", async () => {
render(
<EntityNotebookSurface
{...baseProps}
Expand All @@ -98,7 +102,7 @@ describe("EntityNotebookSurface", () => {
);

expect(await screen.findByTestId("mock-live-notebook")).toHaveTextContent(
"live:softbank:false:user:owner",
"live:softbank:true:user:owner",
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const EntityNotebookView = lazy(() =>
);

const EntityNotebookLiveMount = lazy(() =>
import("@/features/entities/components/notebook/EntityNotebookLiveMount").then((mod) => ({
default: mod.EntityNotebookLiveMount,
})),
import("@/features/entities/components/notebook/EntityNotebookLiveMount").then(
(mod) => ({
default: mod.EntityNotebookLiveMount,
}),
),
);

type EntityNotebookSurfaceProps = {
Expand Down Expand Up @@ -45,7 +47,7 @@ function EntityNotebookSurfaceBase({
shareToken,
entityViewMode,
showViewModeToggle: _showViewModeToggle,
isReadMode,
isReadMode: _isReadMode,
liveNotebookEnabled,
materializingLiveWorkspace,
hasLiveEntity,
Expand All @@ -70,7 +72,6 @@ function EntityNotebookSurfaceBase({
const renderUnifiedNotebook = liveNotebookEnabled && hasLiveEntity;
const shouldRenderFallbackNotebook =
!renderUnifiedNotebook && entityViewMode !== "classic";
const canEditLiveNotebook = canEditNotebook && !isReadMode;

return (
<>
Expand All @@ -79,7 +80,9 @@ function EntityNotebookSurfaceBase({
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium">Live notebook ahead</span>
{notebookDriftUpdatedLabel ? (
<span className="text-xs opacity-80">{notebookDriftUpdatedLabel}</span>
<span className="text-xs opacity-80">
{notebookDriftUpdatedLabel}
</span>
) : null}
</div>
<p className="mt-1.5 leading-6">{notebookDriftMessage}</p>
Expand All @@ -88,7 +91,13 @@ function EntityNotebookSurfaceBase({

{shouldRenderFallbackNotebook ? (
<ErrorBoundary section="Entity notebook">
<Suspense fallback={<div className="py-12 text-center text-sm text-gray-500">Loading notebook...</div>}>
<Suspense
fallback={
<div className="py-12 text-center text-sm text-gray-500">
Loading notebook...
</div>
}
>
<article className="notebook-sheet mt-4">
<EntityNotebookView
entitySlug={entitySlug}
Expand All @@ -104,12 +113,18 @@ function EntityNotebookSurfaceBase({

{renderUnifiedNotebook ? (
<ErrorBoundary section="Live notebook">
<Suspense fallback={<div className="py-12 text-center text-sm text-gray-500">Loading live notebook...</div>}>
<Suspense
fallback={
<div className="py-12 text-center text-sm text-gray-500">
Loading live notebook...
</div>
}
>
<article className="notebook-sheet notebook-sheet-live mt-4">
<EntityNotebookLiveMount
entitySlug={entitySlug}
shareToken={shareToken}
canEdit={canEditLiveNotebook}
canEdit={canEditNotebook}
showReferenceNotebookToggle={false}
onOpenReferenceNotebook={undefined}
viewerOwnerKey={viewerOwnerKey}
Expand Down
Loading
Loading