Skip to content

feat(file-viewer): open a markdown file's new tab as a page of the document - #849

Merged
devswha merged 3 commits into
mainfrom
feat/markdown-reader-tab
Oct 11, 2026
Merged

devswha merged 3 commits into
mainfrom
feat/markdown-reader-tab

Conversation

@devswha

@devswha devswha commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Problem

From mobile/desktop testing of 0.4.7: Open in a new tab on a markdown file (#823) opened a second full copy of the app (sidebar, panes, chat, its own PC connections) with the file viewer as a modal over it. Closing the viewer left the tab as just another app window.

Change

  • src/lib/fileReader.ts: readerHref(machine, path) → ?read=<path>&machine=<id>, readerFromSearch, and resolveFromDocument (a link resolved from the document's folder).
  • src/main.tsx: a ?read= address renders <FileReader> instead of <App> (still inside SettingsProvider, so theme/palette/language apply). Auth is the same cookie the app uses.
  • FileReader: MachineContext from the address, FileViewer in page mode, document.title = file name; links to other files push a new ?read= entry and Back returns (popstate).
  • FileViewer page prop: a <main> instead of the modal; no focus trap, swipe-down, Escape or close button; header keeps name, size, path, Open raw file and Download (no "Open in a new tab" — it is the tab). The modal's "Open in a new tab" for markdown now points at readerHref.
  • ?file= links (deep links, the address while the in-app viewer is open) are unchanged: they still open the app with the viewer.

Tests

  • Unit: fileReader.test.ts (address round trip, ?file= is not a reader address, relative/absolute/~/.. resolution).
  • file-viewer-regression: the new tab is a .file-reader with a read= address, no sidebar/terminal/modal, no close or new-tab control, the file name as title; raw/download/reload as before; a relative link in the document opens other.md in the reader and Back returns. PC ownership: the reader link from a remote-fixture file reads only through /api/machines/remote-fixture/fs/, and a reader address for a PC not set up here never reads /api/fs/.
  • bun run check fast passes. Screenshots at 390 and 1440, dark and light, checked.

🤖 Generated with Claude Code

…cument

Open in a new tab loaded the whole app again with the file viewer over
it. A ?read= address now renders FileReader instead of App: the
document as the page, its name and path with Open raw file and
Download, no sidebar, panes or session connection. Links in the
document open other files there, resolved from its folder, and Back
returns. ?file= links keep opening the app with the viewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 40 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 15f48dcc-6f50-45bf-8dca-9bbf01d5a9a8

📥 Commits

Reviewing files that changed from the base of the PR and between f8cad5b and cae762b.


📒 Files selected for processing (8)
  • CHANGELOG.md
  • scripts/file-viewer-regression.ts
  • src/components/FileReader.tsx
  • src/components/FileViewer.css
  • src/components/FileViewer.tsx
  • src/lib/fileReader.test.ts
  • src/lib/fileReader.ts
  • src/main.tsx

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: devswha/herdr-web-ui/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 53e97c18-434b-4966-93a8-4fa1868ceca0


📥 Commits

Reviewing files that changed from the base of the PR and between 1ed46bb and f8cad5b.



📒 Files selected for processing (8)
  • CHANGELOG.md
  • scripts/file-viewer-regression.ts
  • src/components/FileReader.tsx
  • src/components/FileViewer.css
  • src/components/FileViewer.tsx
  • src/lib/fileReader.test.ts
  • src/lib/fileReader.ts
  • src/main.tsx


Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.




📝 Summary

Summary by CodeRabbit

  • New Features
    • Markdown files opened in a new tab now appear as standalone documents, with the filename and path, plus options to view the raw file or download it.
    • Links to other files resolve relative to the current document. Browser Back and Forward navigation updates the displayed document.
  • Changed
    • Existing ?file=… links continue to open in the app’s file viewer.
📝 Summary

Walkthrough

The change adds a standalone Markdown reader selected by reader URLs. It renders document details and raw-file and download links, resolves document-relative links, and supports browser Back and Forward navigation. The changelog and regression tests describe and cover the reader behavior.

Changes

Standalone Markdown Reader

Layer / File(s) Summary
Reader URL and path resolution
src/lib/fileReader.ts, src/lib/fileReader.test.ts
Adds helpers to build and parse reader URLs and resolve relative paths. Tests cover machine defaults, invalid and legacy parameters, and path resolution cases.
Standalone page and navigation
src/components/FileViewer.tsx, src/components/FileReader.tsx, src/components/FileViewer.css
Adds a page mode to FileViewer and a FileReader component that updates the displayed path and browser history for document links and history events. Adds standalone reader layout styles.
Application routing and regression coverage
src/main.tsx, scripts/file-viewer-regression.ts, CHANGELOG.md
Routes reader URLs to FileReader and other URLs to App. Regression checks cover standalone rendering, relative links, Back navigation, and machine-specific file requests. The changelog describes the reader behavior.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Suggested reviewers: jiunshinn, radicor



Merge Risk: ⚪ Minimal · up to f8cad

Standalone reader navigation is covered by the changed regression test, and no merge-blocking issue was established. Merge after normal checks.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 6 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly describes the main change: opening a Markdown file in a new tab as a dedicated document page.
Description check Passed The description explains the problem, resulting behavior, implementation scope, and test coverage. The Tests section provides the required validation details, although the template heading is named "T…
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.


Full details: Docstring Coverage

Explanation

Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 6 files. (2 skipped: 2 unsupported.)




✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR



🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR



  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

devswha and others added 2 commits October 12, 2026 08:11
… a step above a home-relative folder

Review findings (Codex, pre-merge): a reader address opened without the
app's cookie showed only "could not be opened": the tab asks the bridge's
health first and shows the app's sign-in gate when it is locked. A link
above a ~/ document's folder was folded into ~/, naming another file; the
step up is kept for the PC to expand. The regression's request list is
checked by its length, which does not narrow the array for the typecheck.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@devswha
devswha merged commit ab8b051 into main Oct 11, 2026
9 checks passed
@devswha
devswha deleted the feat/markdown-reader-tab branch October 11, 2026 23:19
devswha added a commit that referenced this pull request Oct 12, 2026
## Change

Release 0.4.8, from main at ca6db6e (20 PRs since v0.4.7: the 0.4.7
mobile walk-through fixes, two contributor PRs and the revived #793).

- `package.json` and `herdr-plugin.toml`: 0.4.8.
- CHANGELOG: `[Unreleased]` becomes `[0.4.8] - 2026-10-12`, with the
compare links. Twelve entries that had no PR link get theirs (#835 #837
#838 #840 #842 #843 #844+#848 #845 #847 #849 #851 #852). A Maintenance
line says the remote-PC runtime is now version 27.
- `release-summaries.json`: 0.4.8 in en/ko/ja/zh/tr (3 new, 6 improved,
8 fixed). The Turkish lines are not checked by a native speaker.
- `REMOTE_BUNDLE_VERSION` 26 -> 27: #836 adds the `pending-hold` server
feature and #787 serves precompressed assets, so `remote-v27` is
published before the app release.

Every PR merged since v0.4.7 is linked in the section: #787 #788 #793
#835 #836 #837 #838 #839 #840 #841 #842 #843 #844 #845 #846 #847 #848
#849 #851 #852.

## Validation

- `bun scripts/release-notes.ts 0.4.8` renders the summaries and the
full changelog (116 lines).
- `bun run check fast`: generated types, typecheck, build and 2800+ unit
tests pass.

## Release

After this merges and its CI on `main` passes: push `remote-v27`, wait
for its release, then run the Release workflow with `0.4.8`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
devswha added a commit that referenced this pull request Oct 12, 2026
…wsers (#854)

## Why
From 0.4.7 mobile testing: a star pinned on the PC did not show on the phone, and settings changed on one device did not reach the other — both lived only in each browser's `localStorage`. The user chose to sync **pins + settings that do not depend on the device**, keeping per-device ones local.

## Design
- **Scope, build-checked.** `SETTING_SCOPE` in `src/lib/settings.ts` marks every setting `shared` or `device` (`satisfies Record<keyof Settings, …>`, so a new setting must be classified). `shared/preferences.ts` lists the shared names; a unit test keeps the two equal.
  - shared: theme, palette, language, sidebarRows, agentOrder, agentCards, agentPins, autoSendQueued, showThinking, quickReplies, showQuickReplies, showSuggestionChip, showUsage, usageCount, usagePace, usageGlance, usageOrder, usageHidden, highlightCode
  - device: terminal*, keyBar*, shortcutOverrides, density, chat font/width, enterSends, keepScreenOn, alert*, voice*, paneClipboard, releasePaneAway, narrowPaneFont, quietOpenedDone, **defaultView** (phone chat vs desktop terminal)
  - Judgment calls for review: `language` (shared; `system` still resolves per browser), `defaultView` (device).
- **Server** (`server/preferences.ts`): one document in `stateDir/preferences.json` (0600, written whole via temp+rename) `{ revision, settings, pins }`. `GET/PUT /api/preferences` (not under `/api/machines`, never forwarded to a bridge). A PUT carries only the changed shared settings, laid over what is held (last write wins per setting), and `pins` only when they changed; unknown names are dropped, ≤500 pins, ≤64 KB. Watch-role devices are refused by the existing read-only gate. Each write is pushed as `{ type: "preferences" }` on `/api/machines/events`.
- **Client** (`src/lib/preferencesSync.ts`, used by `App` after the access gate):
  - load: a server with a document is applied (only the keys it holds); an empty server is never written on load;
  - change: debounced 600 ms, sends only what changed since the last match;
  - event: ignored at or below the last seen revision (own echo), or while a local change is unsent (it goes out next and wins);
  - 403 (watch device) → stop sending, keep receiving; 404 (older server) → sync off.
  - Pins moved to a small store in `sidebarPins.ts` (`useSyncExternalStore`), localStorage kept as cache.
- The `?read=` file reader (#849) does not run any of this.

## Tests
- Unit: `server/preferences.test.ts` (empty start, key stripping, merge, pins kept when omitted, revision, persistence, damaged file, 400/405/413), `preferencesSync.test.ts` (diff, apply), `settings.test.ts` (scope ↔ shared list).
- Contract: `device-access.contract.test.ts` — a watch device GETs 200, PUTs 403.
- Browser: new `preferences-sync-regression.ts` (two browsers, one server): no PUT on load; A's theme change reaches open B live and the PUT holds only `theme`; A's terminal font is never sent and B keeps its own; pins written elsewhere reach an open page; a later page takes the shared theme while keeping its own terminal font and an unchanged shared setting. Registered in `ci-browser.sh` (39 rows).
- `bun run check fast`, integration lane (447 pass), full browser lane — all pass except `narrow-pane-demo-regression`, which fails on `main` on this machine too (scripts after it were run individually and pass).

## Notes
- Interacts with #852 (pins stay in cards) in `AgentSidebar.tsx` — small textual conflict expected; whichever lands second needs a rebase.
- Feature + new route: per the repo's review practice this one should get the adversarial review before merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Maintainer commit 08e6c8b: one reconcile path for every document, held documents during an in-flight save, the document sent when the event stream opens, pins absent until first shared (first-share union, then last write wins), prune at pin time, server commits after the disk write, merged document under 64 KB.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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