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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- **Console for the browser preview** ([#466](https://github.com/raiseCatError/notMyShell/issues/466); [design](docs/design/document-workspace.md#console-runtime-diagnostics-of-the-browser-preview)). In the Chromium preview of a Markdown or HTML document, **`C`** lists what the page itself reported for the document now loaded: script exceptions, `console` errors, warnings and messages, failed loads and HTTP errors, Content Security Policy refusals and requests NMSh refused. It works like Problems — newest first, Tab filters by severity, typing searches, Shift+↑↓ marks, Del clears, Ctrl+C shows exactly what would be copied before copying — and Enter goes to the line in the file when that is certain (an HTML document unchanged since the page loaded); otherwise it says why. The Console only reads: it never turns scripts or the network on (with scripts off, only loading and policy messages appear), accepts no expressions, and ignores `/browser` pages and the Mermaid renderer. Entries stay in memory for the current load, bounded, with the preview's address, query values, tokens and likely secrets redacted before they are kept. The status line counts errors and warnings.
- **`/doctor` reports document rendering** ([#462](https://github.com/raiseCatError/notMyShell/issues/462)). A Documents section names the Chrome or Chromium the previews would use (its version once a browser surface has checked it this session, and whether it is running), the Mermaid it found (path, version, a warning for a major version other than the tested 12), the Mermaid setting and, when diagrams would not be drawn, why. Nothing is launched and the Mermaid bundle is not read, so it says what was found, not that drawing works.
- **Mermaid diagrams in Markdown** ([#462](https://github.com/raiseCatError/notMyShell/issues/462); [design](docs/design/diagrams.md)). A ```` ```mermaid ```` fence is drawn as a real diagram — flowcharts, sequence, class, state, ER, Gantt, pie, journey, git graph, mind map, timeline, quadrant, XY, requirement, Sankey, block and C4 diagrams — in Preview (NMSh's renderer and the Chromium preview), Split's preview pane, the viewer and Hybrid's inactive blocks, with the image backend your terminal supports (Kitty graphics, iTerm2, Sixel, or character art); `i` opens it in the image viewer to zoom and pan. NMSh uses the Mermaid you installed (`npm install -g mermaid`, or `NMSH_MERMAID`) inside the real browser it already drives, and downloads nothing. The page that draws runs no document script, loads nothing from the network or disk and returns only pixels; Mermaid's security settings are locked against directives. Settings → General → **Mermaid diagrams**: *On request* (default: press `D`, or F1 › Diagrams while editing — opening a document never starts a browser), *Automatic*, or *Off*. The fence stays the source: in Hybrid the block under the caret is its exact text, edits are drawn after a pause, and anything not drawn (not asked yet, no browser or Mermaid, a syntax error, too large, a timeout) shows the fence as written with `(diagram not drawn: reason)`. Drawings are cached by content and theme, bounded in number, bytes and time, and the renderer page closes when no document shows a diagram.
- **Hybrid Markdown editing** ([#460](https://github.com/raiseCatError/notMyShell/issues/460)). **F3** in a Markdown document (or `H` from the read-only views) shows it *rendered* — headings, emphasis, links, lists, tables, quotes, code — except for the block your caret (or selection) is in, which shows its exact Markdown source and is fully editable. Move on and it renders again. It is another presentation of the same buffer: every key is the editor's (letters always type; snippets, list/table helpers, undo/redo, hunk review, recovery drafts and Save As all work as in the source editor), the caret lives in source positions, and switching views never changes the text. The smallest safe block is revealed — the whole list for a nested item, the whole table, the whole fence — and a selection shows every block it touches as source. The view is anchored to a source line, so revealing a block does not move what is above it. F3 again returns to the source editor; in the split view (F2) F3 switches the text pane between source and hybrid. Limits: Markdown files only (not HTML); images appear as `[image: alt]`; Up/Down move by source line; pointer input is not handled.
- **Expandable `<details>`** ([#462](https://github.com/raiseCatError/notMyShell/issues/462)). The native Markdown/HTML viewer now shows `<details><summary>…</summary>…</details>` as a disclosure: `▸ Summary` when closed (hiding its content), `▾ Summary` with the content indented when open — in the rendered view, the preview, the split's preview pane and the hybrid view. GitHub-style details, with a blank line and Markdown inside and `</details>` in a later block, are understood (nested ones too); the `open` attribute sets the starting state. **`z`** toggles the summary at or below the top of the screen, **`Z`** all summaries in view; choices are kept by summary text, survive scrolling, resizing and edits above, and never change the file. Scripts, handlers and unknown tags in a details block are inert text. Not yet: pointer clicks (the viewer has no pointer support), and toggling inside the hybrid view (it shows each details as written: closed unless `open`).
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/document-source-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ TextBuffer position (line, UTF-16 column) ⇄ Block.from / Block.to (source li

## What is built on it now
- Heading index (`headingsOf`, `DocumentIndex.headings`): level, plain text, source line, depth (nearest shallower heading) and GitHub-style slug.
- The browser preview's Console (#466) goes to a line only for an HTML document whose text is unchanged since the page loaded (Chrome's 0-based line and UTF-16 column are the buffer's); a Markdown page is generated, so its lines are never offered as the file's.
- `/view file.md#heading`, the viewer's `#` heading prompt with Backspace to go back, and the editor's *Go to heading…* / *Back* (jumps are `SourceAnchor`s).

## Heading anchors: GitHub-style, and how Jekyll/Kramdown differs
Expand Down
13 changes: 13 additions & 0 deletions docs/design/browser-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ clears the selection. The world is recreated after a navigation.
also read with `Target.getTargetInfo` after each load and every 500 ms while the page is open. Titles and addresses shown to the person
have C0, C1 and bidi controls stripped.

### Console (#466)

A preview opened with `console: {documentName}` (the document workspace's preview; never `/browser` tabs, never the diagram
renderer) turns DevTools events the page already receives — `Runtime` and `Log` are enabled on every page for other reasons, so no
domain is added — into `console` events: `Runtime.exceptionThrown`, `Runtime.consoleAPICalled` and `Log.entryAdded` (failed loads and
HTTP errors, CSP refusals, interventions), plus NMSh's own refusals of a request. `Runtime` events count only from the page's default
worlds (tracked from `executionContextCreated` with `isDefault`, cleared with the document), so NMSh's find world and any isolated
world never report. Each event becomes a redacted `ConsoleEntry` (`src/browser/console.ts`) before it leaves the page object: the
preview's address and token become the document's name or a path beside it, other addresses lose credentials, query values and
fragments, likely secrets are replaced, controls are stripped and text is bounded before any pattern runs. Nothing is evaluated,
no object is fetched (`description` only), scripts and network rules are untouched. A cross-document commit emits `document`; the
page holds its first 200 console events until a listener attaches, because the first load starts inside `open()`.

### Dialogs, popups, downloads

`alert` is accepted; `confirm`, `prompt` and `beforeunload` are dismissed. Each emits `dialog` with the message. A download is denied and
Expand Down
2 changes: 1 addition & 1 deletion docs/design/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ no Puppeteer, no Electron, no WebSocket client, no bundled browser.

## Verified by

`tests/browserEngineChrome.test.ts` (a real Chrome 156 here: lazy launch and clean shutdown, idle stop and relaunch, URL policy, redirects, screencast, CSS animation advancing between frames, zoom, input into a form and a link click, wheel, find with scripts off, scripts toggle, popups/dialogs/downloads, remote refusal, cookie isolation, closing), `tests/browserPreviewServer.test.ts`, `tests/browserEngineProtocol.test.ts`, `tests/browserPanel.test.ts`, `tests/browserSurface.test.ts`, `tests/documentPreview.test.ts`. Chrome-dependent tests skip when no browser can start.
`tests/browserEngineChrome.test.ts` (a real Chrome 156 here: lazy launch and clean shutdown, idle stop and relaunch, URL policy, redirects, screencast, CSS animation advancing between frames, zoom, input into a form and a link click, wheel, find with scripts off, scripts toggle, popups/dialogs/downloads, remote refusal, cookie isolation, closing), `tests/browserPreviewServer.test.ts`, `tests/browserEngineProtocol.test.ts`, `tests/browserPanel.test.ts`, `tests/browserSurface.test.ts`, `tests/documentPreview.test.ts`; the document Console: `tests/browserConsole.test.ts`, `tests/browserConsoleView.test.ts`, `tests/browserConsoleChrome.test.ts`. Chrome-dependent tests skip when no browser can start.
4 changes: 3 additions & 1 deletion docs/design/diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ NMSh: bounded PNG decoder (graphics service) ──► Kitty placeholders │ iT
## Not verified, and remaining #462 work

- Physical Ghostty and Zed QA: **NOT RUN** (added to `docs/testing/v024-editor-physical-qa.md`).
- `/doctor` does not report Mermaid's location or version yet.
- `/doctor` reports (Documents section) the browser it would use, the Mermaid it found (path, version, a warning for a major other
than the tested 12), the setting and, when diagrams would not be drawn, the reason in the service's own order. It launches nothing and
does not read the bundle (`probeMermaid` opens and `fstat`s it), so it says what was found, never that drawing works.
- Remaining #462 items outside diagrams: following `#fragment` links inside a rendered document, outline refinements (the optional
persistent outline rail), pointer toggling of `<details>` (and hybrid toggling).
15 changes: 14 additions & 1 deletion docs/design/document-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,22 @@ Limits: code fences are recognised by a line scan (indented code blocks and fenc
- **Currency:** checking is off until Problems is first opened. Then `ProblemsRunner` observes the buffer: each change bumps a revision and schedules a run (500 ms); a run snapshots the lines, aborts any run in flight, and its result is dropped if the revision moved. Opening Problems, Ctrl+R, a save and a Save As re-check (files the document names may have changed; no watchers). The runner is disposed with the editor, on reload and on exit, so no timer or observer outlives the document.
- **Surface:** the dialog lists findings by severity then line with counts and the number of external links not checked; Tab cycles all / problems / errors, typing filters on the message, Del dismisses (until the next report), Esc leaves. In source and preview a jump also scrolls the read-only view to the line; hybrid and the split follow the caret.
- **Independent review (opus subagent, security focus) and what changed:** (1–3) a dangling symlink, or a symlink to a folder this user cannot read, was told apart from other links outside the root (it leaked whether the outside target exists) and `readText` could be raced into following a link or blocking on a FIFO — the path is now walked component by component from the root, each link resolved (even dangling ones, via `readlink`) and confined before the walk continues, and the read opens with `O_NOFOLLOW|O_NONBLOCK`, `fstat`s a regular file and re-checks the real path; (4) heading checks in another file read it once per path and count against a bound; (5) quadratic regexes on hostile lines (80k characters took 17–38 s) — every quantifier is bounded, lines over 20,000 characters are scanned only that far and say so, and CSS is read by a linear scanner; (6) readers of reload, plain Save As-and-close now release the runner; (7) a failing checker no longer throws into the editor; (8–10) false positives and negatives: HTML comments and code spans across lines, TOML front matter, definitions on the next line or in quotes, escaped destinations, `url()` in CSS comments and strings, `url(#id)` checked against ids in HTML, `<base href>`, ids inside `<template>`, the first of duplicate attributes, an undefined `[text][ref]` (CommonMark shows it as text) is only information; the outer link of a badge, nested brackets, inline `<img>` and `@import` are now checked; (11–12) the `image-alt` span is the image's own text and a value spanning lines is not an exact span; (13) the list focus stays on a row when a new report is shorter.
- **Not provided:** a gutter marker in the text, checks of other languages, CSS property validation, link fragments inside another HTML file, following redirects or fetching anything, quick fixes, and the browser console (a separate, gated slice).
- **Not provided:** a gutter marker in the text, checks of other languages, CSS property validation, link fragments inside another HTML file, following redirects or fetching anything, quick fixes. The runtime side is the Console below.
- **Verified by** `tests/diagnostics.test.ts` (each check, hostile paths and symlinks, the filesystem checker, no-folder documents, Unicode and stray CR, HTML and CSS, false-positive stress, bounds) and `tests/problems.test.ts` (runner staleness and disposal, the surface, filters, dismiss, refresh, widths/NO_COLOR/Safe Glyphs, and every mode: source, preview, hybrid, split, edit).

## Console (runtime diagnostics of the browser preview)

`C` in the Chromium preview opens the page's Console ([#466](https://github.com/raiseCatError/notMyShell/issues/466)): what the page itself reported for the document now loaded — script exceptions, `console` errors, warnings and messages, failed loads and HTTP errors, Content Security Policy refusals and NMSh's own refusals. `src/browser/console.ts` (entries, redaction, bounds, source mapping), `src/viewer/consoleDialog.ts` (the dialog), the engine's `console` events ([engine](browser-engine.md#console-466)) and `BrowserSession.console()`.

- **Trust is unchanged by it.** The Console is a reader. Opening, filtering, clearing or copying never calls `setScripts`, never navigates or reloads and sends nothing to the page; there is no expression input and nothing is evaluated. Scripts off (the default) means only loading and policy messages appear, and the header and empty state say so; scripts on remains the explicit `T` of the preview.
- **What is kept:** only entries of the preview opened by this document workspace (`console` is an opt-in of `preview` pages; `/browser` tabs and the Mermaid renderer have none), from the page's own worlds, for the current document load (a navigation, refresh or trust change starts again; a crash adds an entry and the next refresh opens a new page). In memory only, never written, at most 200 records and 256 KB, 2,000 characters a message, eight stack frames; identical consecutive entries are counted, not repeated; dropped ones are counted. Collection starts with the preview (so the first load's errors are there when the Console is opened) and ends when the preview closes; leaving the preview mode closes it.
- **Redaction happens before anything is stored:** the preview's loopback address and its unguessable token become the document's name or a path beside it; other addresses lose user, password, query values and fragment (`data:` keeps only its type); the home folder becomes `~`; `Bearer`/`Basic` credentials, JWTs, common token formats (GitHub, Slack, AWS, Google, `sk-…`, Stripe) and `password=`/`token:`/`"api_key": …`-style values become `‹redacted›`; controls and bidi marks are removed. No cookie, header or body is ever asked for. Redaction is a safety net, not a guarantee: a secret in an unusual shape can survive, which is why copying shows the exact text first.
- **Surface:** the Problems pattern — a list under the editor's text, newest first; each row has the severity glyph (E/W/i in Safe Glyphs), a source tag (JS, log, net, CSP, NMSh, page), the place (dropped below 60 columns, where the detail line carries it) and the message. ↑↓ choose, Tab cycles all / errors and warnings / errors, typing searches, Shift+↑↓ marks, select all (Alt+A, or Cmd+A where the terminal sends it) marks every shown entry, Del clears, Ctrl+C opens the copy review (Enter copies exactly what is shown, Esc goes back), Esc leaves. The header always begins *Console (page runtime, scripts on|off)* so it is not mistaken for Problems' static findings.
- **Going to the source:** Enter moves the caret (a jump Back returns from) and shows the editor only when the place is certain: the entry names the document itself, the document is HTML (served as written, so Chrome's 0-based line and UTF-16 column are the buffer's), the line exists and the text has not changed since the page loaded. Otherwise the reason is shown (outside the document, another file beside it, generated from Markdown, the text changed — `r` refreshes, no line). The text, selection, dirty state and undo history are never touched otherwise.
- **Verified by** `tests/browserConsole.test.ts` (events to entries, redaction, hostile and huge input, bounds, mapping, copy text), `tests/browserConsoleView.test.ts` (fake engine: opt-in, no page calls and unchanged document and undo, key ownership, filters, clear, copy review, exact and refused jumps, document resets, crash and reopen, two documents, listener cleanup, a 20,000-event flood, 30/50/80/120 columns with NO_COLOR and Safe Glyphs, no Chromium) and `tests/browserConsoleChrome.test.ts` (real Chromium: exception, console and 404 entries with the document's lines, no token in any entry, scripts off reports no script activity and nothing leaves the machine, reload, isolated worlds, web pages and the Mermaid renderer report nothing, a flood and a 3 MB message).
- **Independent review (opus subagent, security focus) and what changed:** the trust boundary held (no script, network, navigation or evaluation path; isolation by session and default worlds held against `/browser`, the find world and the Mermaid renderer). Fixed: (1) an address in message text stopped at `()[]{}`, at 2,048 characters or after a letter glued to the scheme, leaving later query values — the whole address up to whitespace or a quote is now rewritten; (2) an address that does not parse kept credentials and query — now only scheme and host remain; (3) the preview token could appear in a location label reached as `localhost`/`[::1]` or inside another address — every loopback name of the preview is recognised and every label is scrubbed like message text; (4) a sibling `index.html` (a file on disk) was treated as the document and Enter could jump to an unrelated line — only the token's root is the document; and, smaller: a lone CR or U+2028/2029 in the text refuses the jump (the browser counts lines differently), a refresh forgets the old load at once, each console argument is cut before joining, copied continuation lines are indented so page text cannot pose as another entry, the blank page after a refused navigation no longer clears the refusal, and `/doctor` reads `package.json` without blocking and gives up on the section after 3 s.
- **Not provided:** evaluating expressions, a network panel (requests, headers, bodies, timings), source maps for generated JavaScript, going to a line in another local file, persistence or export beyond the reviewed copy.

## Hybrid view

`DocumentMode` gains `'hybrid'` (#460), implemented in `src/viewer/HybridView.ts` over the shared source map (`docs/architecture/document-source-map.md`). It is a presentation of the one `TextBuffer`; there is no second editor and no second document.
Expand Down
Loading
Loading