HTML preview: basic in-app viewer + "View in Browser" for advanced docs#10
Closed
wynnwu wants to merge 3 commits into
Closed
HTML preview: basic in-app viewer + "View in Browser" for advanced docs#10wynnwu wants to merge 3 commits into
wynnwu wants to merge 3 commits into
Conversation
HTML summaries previously degraded to a plain-text fallback in the preview pane. They now render styled in a basic, static, private WKWebView (FR-047/048): JavaScript disabled, remote network loads blocked via a scoped WKContentRuleList, no cookies/cache persisted, and link clicks routed to the user's default browser. The preview font controls zoom the render via pageZoom (FR-049). A deterministic, unit-tested HTMLFeatureScanner (FR-050) flags interactive/dynamic constructs (scripts, iframe/embed/object, video/audio, canvas, form controls, inline event handlers) while never flagging plain anchor links, including the stamped YouTube source link. When advanced features are present, a labeled "View in Browser" button appears in the top right of the preview toolbar (FR-051). No new dependency (WebKit is an Apple system framework). 68 tests pass (+11 detector tests); clean release build and bundle. Design lives in specs/003-html-preview-viewer/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR upgrades Sumbee’s preview pane for .html summaries from a plain-text fallback to a basic in-app HTML renderer (via WKWebView), while preserving a “static/private by default” posture and adding a conditional “View in Browser” escape hatch for documents that contain interactive/dynamic features.
Changes:
- Add
HTMLWebView(WebKit-backed) for in-app rendering with JS disabled, non-persistent storage, and remotehttp(s)loads blocked via a content rule list. - Add deterministic
HTMLFeatureScanner(plus unit tests) to detect “advanced” constructs and drive the “View in Browser” toolbar button. - Update docs/spec-kit artifacts plus README/CHANGELOG to reflect the new HTML preview behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
Sources/SumbeeKit/Views/AssetBrowser/MarkdownPreview.swift |
Switch preview rendering for .html to HTMLWebView, compute and use feature-scan result to show “View in Browser”. |
Sources/SumbeeKit/Views/AssetBrowser/HTMLWebView.swift |
New WKWebView wrapper configured for static/private viewing and external link handling. |
Sources/SumbeeKit/Services/HTMLFeatureScanner.swift |
New pure scanner that flags advanced HTML constructs and returns stable, user-facing labels. |
Tests/SumbeeKitTests/HTMLFeatureScannerTests.swift |
New unit tests covering advanced-feature detection and key false-positive guards (anchors/source links). |
README.md |
Update feature description to state that HTML now renders in-app and when “View in Browser” appears. |
CHANGELOG.md |
Add Unreleased entry documenting the new in-app HTML viewer and escape hatch behavior. |
specs/003-html-preview-viewer/spec.md |
New feature spec describing requirements/acceptance criteria and edge cases. |
specs/003-html-preview-viewer/research.md |
New research/decisions doc explaining WebKit choice and privacy/safety stance. |
specs/003-html-preview-viewer/plan.md |
New implementation plan outlining components and integration points. |
specs/003-html-preview-viewer/tasks.md |
New task breakdown tracking detection, viewer, integration, and docs work. |
Comments suppressed due to low confidence (1)
Sources/SumbeeKit/Views/AssetBrowser/MarkdownPreview.swift:176
- If reading the selected asset fails,
contentis set to an error string but HTML previews ignorecontentand renderhtmlRaw(currently reset to ""). This results in a blank preview for unreadable.htmlfiles instead of an error message.
guard let raw = try? String(contentsOf: asset.url, encoding: .utf8) else {
content = "_Couldn’t read this file._"
return
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| completion() | ||
| return | ||
| } | ||
| guard let store = WKContentRuleListStore.default() else { completion(); return } |
The first HTML summary opened in a session rendered as a blank white page; every later one worked, and returning to the first then worked too (100% repro). Root cause: the first loadHTMLString was issued from the WKContentRuleListStore.compileContentRuleList completion handler, whose thread is not guaranteed to be main. WKWebView is main-thread-only, so the off-main load silently painted nothing. Subsequent loads came from the SwiftUI updateNSView pass (main thread) once the rule list was cached, hence only the first was blank. Fix: keep the first paint gated behind the remote-load rule install (so a document is never shown before remote loads are blocked, preserving the privacy guarantee), but hop the compile completion to the main thread via DispatchQueue.main.async before installing the rule and resuming the load. Also add an empty/dedup load guard (skips the transient empty htmlRaw and avoids reloading on zoom changes) and capture the controller weakly. Root cause and fix were confirmed by an independent multi-agent review: the 3 diagnoses agreed on the off-main load (high confidence), and adversarial verification caught that a naive "load eagerly, ungated" fix would reintroduce a remote-load privacy hole by painting before the block installed. Documented as learnings #18. Clean debug + release build; 68 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Sumbee screenshotsCaptured from Main panelSettings - GenerationStyles editorOutput |
wynnwu
added a commit
that referenced
this pull request
Jun 23, 2026
…dvanced docs) HTML summaries now render with their own styling inside the preview pane (a basic, static, private WKWebView) instead of the old plain-text fallback: JavaScript disabled, remote network loads blocked, no cookies/cache, link clicks open in the browser, font controls zoom it. A unit-tested feature scan flags interactive/dynamic HTML (scripts, embeds, media, forms) and shows a top-right "View in Browser" button for those. Includes the main-thread fix for the blank-first-render bug (learnings #18). No new dependency (WebKit is an Apple framework). 68 tests pass. Ships PR #10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




What
HTML summaries previously degraded to a plain-text fallback in the preview pane ("Use Open to view it styled in your browser"), even though HTML is the format users pick specifically for visual output. This adds a basic, in-app HTML viewer with a few small niceties and a smart escape hatch.
Built via the repo's Spec Kit process: see
specs/003-html-preview-viewer/(spec, research, plan, tasks).How it behaves
WKWebView, read-only.allowsContentJavaScript = false)WKContentRuleListscoped to^https?://(no auto-fetched remote images/fonts/trackers, honoring Sumbee's local-first promise).nonPersistent()data store)pageZoom; the size persists, shared with the Markdown preview.HTMLFeatureScannerflags genuinely interactive/dynamic constructs (scripts,iframe/embed/object,video/audio,canvas, form controls, inline event handlers). When any are present, a labeled View in Browser button appears in the top right of the preview toolbar. Plain styled prose, and notably the stamped YouTube source<a>link, do not trigger it.No new dependency
WebKit is an Apple system framework;
Package.swiftis unchanged (the "zero third-party runtime deps" rule holds).Verification
swift buildclean (0 warnings),swift build -c releasecleanswift testgreen: 68 tests (was 57; +11HTMLFeatureScannerTests, including the anchor-link false-positive guard)./scripts/bundle.shassemblesdist/Sumbee.appcleanlyOut of scope
Live styled rendering during streaming (still streams as text), in-app HTML editing/printing, and an opt-in to run JS/remote loads in-app (the browser is that path).
🤖 Generated with Claude Code