Skip to content

fix: the four dead controls, the empty wiki, and failures reported where they happen - #29

Merged
protonspy merged 3 commits into
mainfrom
fix/desktop-dead-controls
Aug 2, 2026
Merged

fix: the four dead controls, the empty wiki, and failures reported where they happen#29
protonspy merged 3 commits into
mainfrom
fix/desktop-dead-controls

Conversation

@protonspy

@protonspy protonspy commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Group 1 of plans/desktop-ui.md — the group that plan says ships on its own, because until it lands there is no way to create a page in the packaged application. Tasks 1.1 to 1.5, all ticked in the plan.

What changed

1.1 — the dialogs. Ask.tsx asks the shell's questions through a native <dialog> opened with showModal(), so the focus trap, Escape, and returning focus to whatever held it before are the platform's rather than hand-rolled. Every globalThis.prompt / globalThis.confirm call site is converted: new page, rename, delete, the recording occasion, and the source retitle. Electron implements none of the three, which is why all four controls did nothing at all — and produced no error either, since the throw was outside the try.

The record occasion keeps 4.16's fallback: not answering the box records under the timestamp rather than refusing, and the button says so — Record without a name — instead of saying "Cancel" and recording anyway.

1.2 — the lint rule. prompt, confirm and alert are banned under src/renderer, in their bare form and through globalThis. / window. / self.. The test runs the repository's real config rather than a copy of the rule, because every way of getting this wrong — a glob missing .tsx, the property form going unnoticed, the block sitting after prettier — looks like a working config from the inside.

1.3 — the wiki's own pages. scaffold() seeds wiki/index.md and wiki/changelog.md. The skills tell the agent to link a new page from the index and the checks read both, and neither file existed until something happened to write one. Neither seed contains a wikilink, because checkRecords reads every [[…]] in the changelog as a page that should exist — a test asserts a brand-new project has nothing at all for ow check to report. log.md stays absent: it is a log, and an empty one is noise.

1.4 — an empty wiki explains itself. This wiki has no pages yet reads as a defect and invites the conclusion that the application is broken. It now says the thing that is true and is said nowhere else in the product — this window does not write pages, your agent does — and shows the project path to open in a harness.

1.5 — failures where they happened. notices.ts keeps one notice per place instead of one string for the whole window. A failed drop no longer erases a failed page load; a failure inside a pane is reported inside it; a source retitle that used to reject into nothing at all now says why on its own row; and a rename's "repointed the links on …" stops arriving through the error channel in the same red box a failed rename used.

How it was verified

pnpm test:coverage (the whole workspace, 76% floor per package, exit 0) · pnpm lint · pnpm run typecheck · npx @protonspy/scc validate — no findings.

code-review and security-review were run on the diff, then re-run after the fixes. Three findings, all closed:

  • blocker — both dialog buttons were submit buttons with Cancel first in tree order, so Enter in the text field submitted through Cancel and discarded the typed answer. The reviewer reproduced it in a real Chromium engine, and confirmed the fix the same way. The buttons now come from buttonsFor, where the rule is written down and where the tests can pin it.
  • medium → highseedWiki wrote with existsSync + writeFileSync, which follows a dangling symlink planted at a seed path straight out of the project. Fixed with assertWithin plus an lstat guard: O_CREAT | O_EXCL refusing a symlink is a POSIX rule with no Win32 equivalent, and Windows is what this ships on, so the guard cannot rest on it.
  • low — a question left pending when the component that asked it unmounts is now settled rather than left hanging.

One thing worth a second opinion

Escape on the record occasion dialog starts a recording. That is the plan's own wording for 1.1 — "cancelling names the recording by timestamp rather than refusing to record" — and 4.16's rule that capture is worth more than a naming rule. The cancel button says so, but Escape does not get to say anything. The persistent recording indicator makes an unintended one visible immediately, which is why I implemented it as written; if you would rather Escape meant "don't record", it is one label and one branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HBt5LRVJd5Z7nagJGRwvSC

Summary by CodeRabbit

  • New Features

    • Added native dialogs for naming recordings, creating, renaming, retitling, and deleting pages.
    • Added clearer, location-specific error and status messages.
    • New projects now include starter wiki index and changelog files.
    • Added guidance when a wiki is empty, including its location.
  • Improvements

    • Existing wiki files are preserved during project setup.
    • Improved validation and feedback for dialog actions.
  • Tests

    • Expanded coverage for dialogs, notices, and project scaffolding.

protonspy and others added 3 commits August 1, 2026 21:49
… happen

Group 1 of plans/desktop-ui.md — the part of the desktop UI plan that ships on
its own, because until it lands there is no way to create a page in the
packaged application.

- `Ask.tsx` asks the shell's questions through a native `<dialog>` opened with
  `showModal`, so the focus trap, Escape and the return of focus are the
  platform's rather than hand-rolled. Every `globalThis.prompt` /
  `globalThis.confirm` call site is converted: new page, rename, delete, the
  record occasion, and the source retitle. Electron implements none of the
  three, which is why all four controls did nothing at all.
- The record occasion keeps 4.16's fallback: not answering the box records
  under the timestamp rather than refusing, and the button says so instead of
  saying "Cancel" and recording anyway.
- A lint rule bans `prompt`, `confirm` and `alert` under `src/renderer`, in
  both their bare and `globalThis.`/`window.`/`self.` forms. The test runs the
  repository's real config, because a rule that does not fire during
  `pnpm lint` is worth nothing.
- `scaffold()` seeds `wiki/index.md` and `wiki/changelog.md`. The skills tell
  the agent to link a new page from the index and the checks read both, and
  neither file existed until something happened to write one. `log.md` stays
  absent: it is a log, and an empty one is noise.
- An empty wiki explains itself — this window does not write pages, the agent
  does — and shows the path to open in a harness. That is the central fact
  about the product and it was said nowhere.
- Failures are reported where they happened rather than in one line above
  every pane: `notices.ts` keeps one notice per place, so a failed drop no
  longer erases a failed page load, and a rename's "repointed the links on …"
  stops arriving through the error channel.

Verified: `pnpm test:coverage` (1119 tests, the 76% floor per package),
`pnpm lint`, `pnpm typecheck`, `npx @protonspy/scc validate` — all clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBt5LRVJd5Z7nagJGRwvSC
- **Enter submitted the wrong button.** Both dialog buttons were submit
  buttons with cancel first in tree order, and implicit submission goes
  through the form's default button — the first submit button — not the
  focused one. Typing a slug and pressing Enter closed the box with cancel's
  empty `returnValue` and threw the answer away, in every prompt this plan
  adds. The buttons now come from `buttonsFor`, which is where the rule is
  written down and what the new tests assert: exactly one button submits, and
  it is the one that answers.
- **`seedWiki` wrote without the project's own confinement.** A dangling
  symlink planted at `wiki/index.md` is exactly the case `existsSync` answers
  "no" to, and the write then followed it out of the project. It now goes
  through `assertWithin` like every other writer in the package, and writes
  with `wx` — `O_CREAT | O_EXCL`, which the kernel refuses on a symlink of any
  kind, so the check and the write cannot disagree.
- A question left pending when the component that asked it unmounts is settled
  rather than left hanging.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBt5LRVJd5Z7nagJGRwvSC
… O_EXCL

The second security round was right: `O_CREAT | O_EXCL` refusing a symlink is
a POSIX rule with no Win32 equivalent, and Windows is what this ships on. The
guard is now `lstat`, which answers about the name rather than about what the
name points at — anything there at all, symlink or directory or file, and the
seed is not written. That holds identically on every platform. `wx` stays for
the sliver between the two calls.

Both regression tests also scaffolded into a directory holding only `wiki/`,
which `isEmptyOrProject` refuses — they would have failed in CI, where the
symlink one actually runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBt5LRVJd5Z7nagJGRwvSC
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The desktop renderer now uses native dialogs and location-scoped notices. Scaffolding creates initial wiki index and changelog files safely. ESLint prevents renderer use of browser blocking dialogs.

Changes

Desktop renderer UI

Layer / File(s) Summary
Native dialog system
apps/desktop/src/renderer/dialogs.ts, apps/desktop/src/renderer/Ask.tsx, apps/desktop/tests/dialogs.spec.ts, apps/desktop/src/renderer/tokens.css
Adds typed questions, answer handling, native modal rendering, recording-name fallback behavior, destructive actions, validation, styling, and tests.
Notice and renderer operation flows
apps/desktop/src/renderer/notices.ts, apps/desktop/src/renderer/App.tsx, apps/desktop/src/renderer/Sources.tsx, apps/desktop/tests/notices.spec.ts, apps/desktop/src/renderer/tokens.css
Routes operation failures and notes to shell, wiki, page, recording, source, and inbox locations. Adds the empty wiki view and source retitling through dialogs.
Renderer dialog enforcement
eslint.config.js, apps/desktop/package.json, apps/desktop/tests/no-dead-dialogs.spec.ts, plans/desktop-ui.md
Blocks browser dialog APIs in renderer code, adds integration coverage, adds the ESLint development dependency, and marks desktop UI tasks complete.

Wiki scaffolding

Layer / File(s) Summary
Wiki seed content and scaffold implementation
packages/access/src/store/index.ts, packages/access/src/scaffold.ts
Adds index and changelog seed content. Scaffolding writes missing wiki files with path checks, exclusive creation, and race handling, and reports written paths.
Wiki seed validation
packages/access/tests/scaffold.spec.ts
Tests seed creation, preservation, occupied paths, dangling symlinks, normalized paths, and clean project checks.

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

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant useDialogs
  participant Ask
  participant HTMLDialogElement
  App->>useDialogs: ask(question)
  useDialogs->>Ask: render question
  Ask->>HTMLDialogElement: showModal()
  HTMLDialogElement-->>Ask: submit or cancel returnValue
  Ask-->>useDialogs: resolve answer
  useDialogs-->>App: return answer
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 clearly summarizes the main changes: fixing desktop controls, improving the empty wiki, and localizing failure reporting.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/desktop-dead-controls

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

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/Sources.tsx (1)

41-65: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Render {dialog} regardless of the error/rows/empty branch.

{dialog} is rendered only in the final return branch, at Line 62. If a retitle dialog is pending (an ask() promise is awaiting an answer in SourceItem) and a subsequent load() triggered by reloadKey fails or returns an empty list, Sources re-renders through the error (Line 41), !rows (Line 42), or rows.length === 0 (Line 43-47) branch instead. None of those branches include {dialog}.

Sources does not unmount in that case, so the unmount-settle cleanup in useDialogs() (Ask.tsx, Line 51) never runs. The open <Ask> element disappears from the DOM without its onClose firing, and the pending ask() promise never resolves. The retitle() call that awaits it in SourceItem hangs indefinitely.

Render {dialog} unconditionally so a pending question always stays visible and answerable, independent of the surrounding content state.

🔧 Proposed fix
-  if (error) return <p className="error">{error}</p>;
-  if (!rows) return <p className="empty">Reading the sources…</p>;
-  if (rows.length === 0) {
-    return (
-      <p className="empty">No sources yet. Drop a file on this window, or record something.</p>
-    );
-  }
-
-  return (
-    <>
-      <ul className="list">
-        {rows.map((row) => (
-          <SourceItem
-            key={row.id}
-            row={row}
-            ask={ask}
-            onOpenPage={onOpenPage}
-            onChanged={() => void load()}
-          />
-        ))}
-      </ul>
-      {dialog}
-    </>
-  );
+  return (
+    <>
+      {error ? (
+        <p className="error">{error}</p>
+      ) : !rows ? (
+        <p className="empty">Reading the sources…</p>
+      ) : rows.length === 0 ? (
+        <p className="empty">No sources yet. Drop a file on this window, or record something.</p>
+      ) : (
+        <ul className="list">
+          {rows.map((row) => (
+            <SourceItem
+              key={row.id}
+              row={row}
+              ask={ask}
+              onOpenPage={onOpenPage}
+              onChanged={() => void load()}
+            />
+          ))}
+        </ul>
+      )}
+      {dialog}
+    </>
+  );
🤖 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/desktop/src/renderer/Sources.tsx` around lines 41 - 65, Update the
Sources component’s early error, loading, and empty-state returns so they also
render the dialog from useDialogs. Ensure dialog remains mounted in every render
branch, including the final list branch, so pending ask() promises remain
answerable.
🤖 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/desktop/src/renderer/dialogs.ts`:
- Around line 12-14: Update the doc comment in the dialogs module to reference
Ask.tsx instead of the stale Dialogs.tsx name, leaving the rest of the comment
unchanged.

---

Outside diff comments:
In `@apps/desktop/src/renderer/Sources.tsx`:
- Around line 41-65: Update the Sources component’s early error, loading, and
empty-state returns so they also render the dialog from useDialogs. Ensure
dialog remains mounted in every render branch, including the final list branch,
so pending ask() promises remain answerable.
🪄 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: 51bea4a8-0584-424c-af0b-e9ad7acbe53e

📥 Commits

Reviewing files that changed from the base of the PR and between 262348a and fb2af74.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/desktop/package.json
  • apps/desktop/src/renderer/App.tsx
  • apps/desktop/src/renderer/Ask.tsx
  • apps/desktop/src/renderer/Sources.tsx
  • apps/desktop/src/renderer/dialogs.ts
  • apps/desktop/src/renderer/notices.ts
  • apps/desktop/src/renderer/tokens.css
  • apps/desktop/tests/dialogs.spec.ts
  • apps/desktop/tests/no-dead-dialogs.spec.ts
  • apps/desktop/tests/notices.spec.ts
  • eslint.config.js
  • packages/access/src/scaffold.ts
  • packages/access/src/store/index.ts
  • packages/access/tests/scaffold.spec.ts
  • plans/desktop-ui.md

Comment on lines +12 to +14
* The components in `Dialogs.tsx` arrange these and decide nothing. What is
* here is the part a test can reach: what each question says, which button
* says what, and what closing the box means.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix stale file reference in doc comment.

Line 12 refers to Dialogs.tsx. The component that consumes these questions is Ask.tsx. Update the comment to name the correct file, so a reader who looks for Dialogs.tsx does not conclude it is missing.

📝 Proposed fix
- * The components in `Dialogs.tsx` arrange these and decide nothing. What is
+ * The components in `Ask.tsx` arrange these and decide nothing. What is
📝 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
* The components in `Dialogs.tsx` arrange these and decide nothing. What is
* here is the part a test can reach: what each question says, which button
* says what, and what closing the box means.
* The components in `Ask.tsx` arrange these and decide nothing. What is
* here is the part a test can reach: what each question says, which button
* says what, and what closing the box means.
🤖 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/desktop/src/renderer/dialogs.ts` around lines 12 - 14, Update the doc
comment in the dialogs module to reference Ask.tsx instead of the stale
Dialogs.tsx name, leaving the rest of the comment unchanged.

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.

1 participant