Skip to content

Add app-console runme helpers for run-all and clear-outputs#81

Closed
hamelsmu wants to merge 15 commits intorunmedev:mainfrom
hamelsmu:feature/issue-55-review-fixes
Closed

Add app-console runme helpers for run-all and clear-outputs#81
hamelsmu wants to merge 15 commits intorunmedev:mainfrom
hamelsmu:feature/issue-55-review-fixes

Conversation

@hamelsmu
Copy link
Collaborator

Summary

  • add a new runme namespace in the App Console with:
    • runme.getCurrentNotebook()
    • runme.runAll([notebookOrUri])
    • runme.clearOutputs([notebookOrUri])
    • runme.help()
  • preserve custom global help() in JSKernel so App Console namespace help remains visible
  • add unit tests for runme console helper behavior
  • add quick-start hint snippets for the new commands in the empty notebook view

Testing

  • cd web/app && npx vitest run src/lib/runtime/runmeConsole.test.ts
  • cd web && pnpm run build
  • cd web/app && bash test/browser/test-smoke.sh 5173
  • browser verification with agent-browser:
    • loaded a notebook and ran runme.help()
    • ran runme.runAll("local://...") and confirmed console summary output
    • ran runme.clearOutputs("local://...") and confirmed console summary output

Notes

  • runme.getCurrentNotebook() can return null when no active doc is resolved; runme.runAll/clearOutputs accept an explicit notebook URI for this case.
  • Existing unrelated test/typecheck failures remain on this branch (not introduced by this change).

Closes #46

hamelsmu and others added 14 commits February 6, 2026 14:06
- Add MarkdownCell component with edit/render mode toggle
- Double-click rendered markdown to enter edit mode
- Press Escape or click away to render markdown
- Fix Monaco editor layout issue causing horizontal expansion
  - Remove window.innerWidth fallback in adjustHeight()
  - Add contain: inline-size to prevent feedback loop
  - Add min-w-0/max-w-full CSS constraints throughout
- Add tests for MarkdownCell component

Amp-Thread-ID: https://ampcode.com/threads/T-019c2c77-9f58-726f-9c8d-21d4e1b6aff1
Co-authored-by: Amp <amp@ampcode.com>
- Fix memo comment to clarify React.memo semantics (return true = skip re-render)
- Add useEffect to enforce 'empty cells must be in edit mode' invariant
  - Handles external changes (undo/redo, sync) that clear cell value
  - react-components version also resets on cell identity change
- Add role='button' for accessibility on rendered markdown view
- Guard keyboard handler to only trigger on container, not nested elements
  - Prevents Enter/Space from breaking links inside rendered markdown

Amp-Thread-ID: https://ampcode.com/threads/T-019c2c77-9f58-726f-9c8d-21d4e1b6aff1
Co-authored-by: Amp <amp@ampcode.com>
…unmedev#55)

Implements ContentsNotebookStore using the Go backend ContentsService
via ConnectRPC JSON over HTTP POST. This provides a fully automatable
storage backend that works in CI/headless environments without
requiring browser user gestures.

Features:
- Full NotebookStore implementation (list, load, save, create, rename, getMetadata)
- SHA256-based conflict detection with fork-on-conflict strategy
- Markdown file support via injectable MarkdownConverter interface
- IGNORED_DIRECTORIES and hidden file filtering in list()
- ConflictResult return type moved to shared NotebookStore interface
- UTF-8 safe base64 encoding/decoding for non-ASCII content
- ContentsStoreContext + provider for React integration
- Wired into App.tsx NotebookStoreInitializer
- 56 comprehensive tests covering all operations

URI scheme: contents://<host:port>/file|dir/<encodedPath>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…4 in create()

Remove leaked FilesystemNotebookStore import/field/setter from AppState.ts
that references non-existent fs.ts on this branch (build breaker). Also
use utf8ToBase64() consistently in create() instead of raw btoa().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ion test infrastructure

- Fix ContentsNotebookStore.rename() to preserve markdown extensions
- Add ContentsRpcError typed error class for robust conflict detection (HTTP 409)
- Remove GoogleDrivePickerButton from Explorer header per UX review
- Expand App Console commands: explorer.mountDrive(), openPicker(), removeFolder(), help()
- Use resolved agent endpoint instead of hardcoded VITE_DEFAULT_AGENT_ENDPOINT
- Filter dot-directories in ContentsNotebookStore.list()
- Fix base64 encoding with 32KB chunked processing for large payloads
- Add storeResolver.ts for URI-based routing to correct store
- Add integration test notebooks (.runme.md) for CLI and browser testing
- Add agent-browser test script with auth bypass pre-flight check
- Fix .env.development port mismatch (9988 -> 9977)
- Add 20 new unit tests (62 total in contents.test.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused isContentsUri import in NotebookContext.tsx (noUnusedLocals)
- Use buildContentsUri helper in AppConsole instead of manual regex parsing
- Include refId in MarkdownCell memo comparator to prevent stale rendered state
- Introduce ResolvedStore type alias to remove unsafe cast in storeResolver
- Fix documentation paths in browser test script and README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Hamel Husain <hamel.husain@gmail.com>
Restore utf8ToBase64() chunked encoding helper that was lost during
rebase. Document the known unsafe cast in storeResolver.ts (Copilot
review feedback - proper fix requires LocalNotebooks to implement
full NotebookStore interface).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
App.tsx calls appState.setFilesystemStore() but the method did not exist
on AppState, causing a runtime TypeError that resulted in a blank screen.

Amp-Thread-ID: https://ampcode.com/threads/T-019c3511-3c46-7022-871c-8cda3ec09592
Co-authored-by: Amp <amp@ampcode.com>
- test-smoke.sh: browser-based smoke test that checks for JS errors,
  verifies core UI elements render, and detects blank screen issues
- typecheck script: runs tsc against tsconfig.app.json (not wired into
  build yet due to pre-existing type errors)

These would have caught the setFilesystemStore TypeError (c4b4d48).

Amp-Thread-ID: https://ampcode.com/threads/T-019c3511-3c46-7022-871c-8cda3ec09592
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Hamel Husain <hamel.husain@gmail.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c3550-58d7-71cb-8e0f-dffaeb9ca1e0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Hamel Husain <hamel.husain@gmail.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c3550-58d7-71cb-8e0f-dffaeb9ca1e0
Co-authored-by: Amp <amp@ampcode.com>
Shows a red toast at the top of the viewport when a user tries to run a
code cell but the Runme backend server is unreachable. The toast fires in
two scenarios: when no runner/endpoint is configured, and when the
WebSocket connection to the backend fails.

New files:
- app/src/lib/toast.ts (global event emitter)
- app/src/components/Toast.tsx (GlobalToast UI component)

Closes runmedev#71

Amp-Thread-ID: https://ampcode.com/threads/T-019c54ff-b8db-746a-88b9-3595e525ea3d
Co-authored-by: Amp <amp@ampcode.com>
Copilot AI review requested due to automatic review settings February 13, 2026 15:06
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c87f2aade

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +34 to +38
if (isFsUri(uri) && fsStore) {
return fsStore;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return localStore as unknown as NotebookStore | null;

Choose a reason for hiding this comment

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

P1 Badge Return null when specialized store is unavailable

resolveStore falls back to localStore for fs:///contents:// URIs whenever the dedicated store is not initialized yet, which makes callers (for example NotebookContext loaders) invoke LocalNotebooks methods with non-local:// URIs and fail at runtime; this can surface as failed notebook loads and current-doc reset during startup races. For non-local schemes, the resolver should return null until the matching store is ready instead of silently routing to the local store.

Useful? React with 👍 / 👎.

Comment on lines 335 to 339
help: () => {
return [
"Available namespaces:",
" runme - Notebook helpers (run all, clear outputs)",
" explorer - Manage workspace folders and notebooks",

Choose a reason for hiding this comment

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

P2 Badge Print help text to the console output

This help implementation only returns a string, but JSKernel.run does not print expression return values, so entering help() (the prompt’s suggested command) shows no help text to the user. The new top-level help path should write through sendStdout (like runme.help) so command discovery actually works in the app console.

Useful? React with 👍 / 👎.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the app’s developer-facing console and notebook UX by introducing a new runme helper namespace in the App Console (run all cells / clear outputs), plus supporting changes to notebook storage routing (fs:// vs contents://), markdown cell rendering, and test/fixture coverage for UI + large payload scenarios.

Changes:

  • Add runme.* App Console helpers (getCurrentNotebook, runAll, clearOutputs, help) and preserve a global help() in JSKernel.
  • Improve notebook handling: store resolution by URI scheme, conflict-result type plumbing, cell refId normalization for JSON fixtures, debounced auto-save, and base64 chunking helper.
  • Add UI/testing support: empty-state quick-start snippets, global toast, browser smoke/integration scripts, and several fixture notebooks (including large-payload).

Reviewed changes

Copilot reviewed 39 out of 41 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds lock entries for new markdown-related dependencies (e.g., remark-gfm).
packages/react-components/src/components/index.tsx Re-exports MarkdownCell.
packages/react-components/src/components/Actions/tests/MarkdownCell.test.tsx Adds unit tests for the react-components MarkdownCell.
packages/react-components/src/components/Actions/MarkdownCell.tsx Introduces Jupyter-style in-place markdown rendering for react-components.
packages/react-components/package.json Adds remark-gfm dependency.
app/vite.config.mts Ignores notebook fixture writes to avoid dev full-page reload loops.
app/test/fixtures/notebooks/ui-test.runme.md Adds UI/UX test notebook fixture.
app/test/fixtures/notebooks/subdirectory/nested-notebook.json Adds nested fixture to test explorer folder navigation.
app/test/fixtures/notebooks/large-payload-test.runme.md Adds >100KB payload fixture for large payload/base64 testing.
app/test/fixtures/notebooks/hello-world.json Adds simple JSON notebook fixture.
app/test/fixtures/notebooks/cell-types.json Adds multi-language + markdown features JSON fixture.
app/test/fixtures/notebooks/cell-types-test.runme.md Adds multi-language .runme.md fixture.
app/test/fixtures/notebooks/basic-test.runme.md Adds basic .runme.md fixture.
app/test/browser/test-smoke.sh Adds startup/blank-screen smoke test using agent-browser.
app/test/browser/test-notebook-ui.sh Adds notebook UI integration test using fixtures + screenshots.
app/test/browser/test-app-console-commands.sh Adds App Console integration test for explorer commands.
app/test/browser/README.md Documents browser integration test setup and fixtures.
app/src/storage/storeResolver.ts Adds URI-scheme routing to pick the correct NotebookStore.
app/src/storage/notebook.ts Changes NotebookStore.save() to return ConflictResult.
app/src/storage/fs.ts Ensures loaded JSON notebooks have stable refIds for cells.
app/src/storage/drive.ts Updates Drive store save() to return ConflictResult.
app/src/storage/contents.ts Adds chunked UTF-8→base64 helper and uses it for create-write path.
app/src/lib/toast.ts Adds lightweight global toast event bus.
app/src/lib/runtime/runmeConsole.ts Implements runme.* console helper API (run-all / clear-outputs).
app/src/lib/runtime/runmeConsole.test.ts Adds unit tests for runmeConsole behavior.
app/src/lib/runtime/jsKernel.ts Preserves a custom global help() if provided; otherwise uses default.
app/src/lib/runtime/AppState.ts Reorders localNotebooks field and setter placement.
app/src/lib/notebookData.ts Adds store abstraction, debounced persistence, and backend-error toasts.
app/src/lib/aisreClient.ts Adds parser-service deserialize helper.
app/src/index.css Adds selection styling for quick-start code snippet.
app/src/contexts/NotebookContext.tsx Uses resolveStore and avoids persisting on initial loads.
app/src/contexts/CurrentDocContext.tsx Allows direct contents:// and fs:// doc resolution from URL.
app/src/components/Workspace/WorkspaceExplorer.tsx Improves tree row layout/wrapping; avoids store mirroring for fs/contents docs.
app/src/components/Toast.tsx Adds global toast UI component.
app/src/components/AppConsole/AppConsole.tsx Adds runme + expanded explorer helpers; captures output for tests; updates console banner/help.
app/src/components/Actions/MarkdownCell.tsx Updates app markdown cell for accessibility + edit/render switching.
app/src/components/Actions/Editor.tsx Switches Monaco theme to vs-dark; avoids bad width fallbacks; adjusts wrapper styling.
app/src/components/Actions/Actions.tsx Updates markdown/code cell chrome, adds empty-state quick-start console snippets.
app/src/App.tsx Wires global toast; passes agent endpoint to initializer for Contents store.
app/package.json Adds typecheck script.
app/.env.development Updates default agent/runner endpoint ports to 9977.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

app/src/storage/fs.ts:387

  • FilesystemNotebookStore implements NotebookStore, but its save() method still returns Promise<void> and throws on conflict. With the updated NotebookStore.save(): Promise<ConflictResult>, this store should return a ConflictResult (and ideally not throw for the conflict case) so callers can handle conflicts consistently across stores.
    const notebook = fromJsonString(parser_pb.NotebookSchema, text, {
      ignoreUnknownFields: true,
    });
    ensureCellRefIds(notebook);
    return notebook;
  }

  async save(uri: string, notebook: parser_pb.Notebook): Promise<void> {
    const parsed = parseFsUri(uri);

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

Comment on lines +17 to 28
/**
* Result returned by save() when a conflict is detected.
* Stores that don't support conflict detection return `{ conflicted: false }`.
*/
export interface ConflictResult {
conflicted: boolean;
conflictFileName?: string;
}

export interface NotebookStore {
save(uri: string, notebook: parser_pb.Notebook): Promise<void>;
save(uri: string, notebook: parser_pb.Notebook): Promise<ConflictResult>;
load(uri: string): Promise<parser_pb.Notebook>;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

NotebookStore.save() now returns Promise<ConflictResult>, but there are still implementations (e.g. FilesystemNotebookStore and ContentsNotebookStore) returning Promise<void>, which will cause TypeScript errors and/or inconsistent conflict handling. Update all NotebookStore implementers so their save() signature matches and they return { conflicted: false } on success and { conflicted: true, ... } (instead of throwing) when a conflict is detected.

Copilot uses AI. Check for mistakes.
let binary = "";
for (let i = 0; i < bytes.length; i += CHUNK) {
const chunk = bytes.subarray(i, i + CHUNK);
binary += String.fromCharCode(...chunk);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The new utf8ToBase64() helper is added to avoid stack/size issues, but ContentsNotebookStore.save() still uses btoa(json) (see below in the file), so saving large notebooks can still hit the same failure mode. Consider switching save() to use utf8ToBase64() as well, and update save() to return ConflictResult to match the updated NotebookStore interface.

Suggested change
binary += String.fromCharCode(...chunk);
for (let j = 0; j < chunk.length; j++) {
binary += String.fromCharCode(chunk[j]);
}

Copilot uses AI. Check for mistakes.
return data;
},
[emit, notebookStore],
[emit, notebookStore, contentsStore],
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

ensureNotebook uses fsStore when calling resolveStore(...), but fsStore is missing from the hook dependency array. This can leave NotebookData instances bound to a stale store if the filesystem store initializes after the callback is memoized. Add fsStore to the dependency list (and any other referenced store values).

Suggested change
[emit, notebookStore, contentsStore],
[emit, notebookStore, fsStore, contentsStore],

Copilot uses AI. Check for mistakes.
Comment on lines 298 to 329
// Load any notebooks that were open last session once a store is available.
useEffect(() => {
if (!notebookStore) {
if (!notebookStore && !contentsStore) {
return;
}
const loadExisting = async () => {
for (const item of openNotebooks) {
const entry = storeRef.current.get(item.uri);
if (entry?.loaded) {
continue;
}
if (!entry) {
// We persisted an open notebook URI without a corresponding model in memory.
// Skip here; the model should be created elsewhere before loading.
continue;
}
const store = resolveStore(item.uri, notebookStore, fsStore, contentsStore);
if (!store) {
continue;
}
try {
const [metadata, notebook] = await Promise.all([
notebookStore.getMetadata(item.uri),
notebookStore.load(item.uri),
store.getMetadata(item.uri),
store.load(item.uri),
]);
entry.data.loadNotebook(notebook);
entry.data.loadNotebook(notebook, { persist: false });
entry.loaded = true;
} catch (error) {
console.error("Failed to load open notebook", item.uri, error);
}
}
};
void loadExisting();
}, [notebookStore, openNotebooks]);
}, [notebookStore, contentsStore, openNotebooks]);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

This effect calls resolveStore(item.uri, notebookStore, fsStore, contentsStore) but fsStore is not included in the dependency array. If fsStore becomes available after the effect has run, open fs:// notebooks may never load (or may load via the wrong store). Include fsStore (and any other referenced store values) in the deps.

Copilot uses AI. Check for mistakes.
Comment on lines +635 to +641
<Text
size="2"
className="mt-1 p-2 font-bold text-gray-400 font-mono"
data-testid="sequence-label"
>
[{sequenceLabel}]
</Text>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

sequenceLabel returns a single space when no sequence is set, but the UI now always renders it inside brackets ([{sequenceLabel}]), which produces a visible "[ ]" placeholder for most cells. Either only render the label when a real sequence exists, or have sequenceLabel return an empty string and skip the brackets when blank.

Copilot uses AI. Check for mistakes.
@hamelsmu
Copy link
Collaborator Author

Superseded by #82, which replays only the issue #46 changes on top of current main.

@hamelsmu hamelsmu closed this Feb 13, 2026
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.

Clear all cells / Run all cells

1 participant