Tauri wraps a WebView in HTML. That's a browser pretending not to be a browser. The manifesto says: "The client renders. The server serves text." A WebView renders HTML — that's the server's job. The client should render .gph natively.
The client is not a browser. It's a protocol client. Like a mail client renders emails, like a Gemini client renders Gemtext, the Burrow client renders .gph. No HTML. No CSS. No WebView. No JavaScript. Native text rendering with native UI.
┌────────────────────────────────────────────────────────────────┐
│ macOS App (Swift/AppKit) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ NSToolbar │ │
│ │ [← Back] [→ Forward] [⌂ Home] [gph://localhost/~bruno]│ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┬────────────────────────────────────────────┐ │
│ │ NSOutlineView│ NSScrollView + NSTextView │ │
│ │ (sidebar) │ (content — rendered .gph) │ │
│ │ │ │ │
│ │ BURROWS │ localhost / ~bruno │ │
│ │ / ~bruno/ │ ~bruno/ │ │
│ │ / ~burrow/ │ ITAD, web craft, en te veel meningen... │ │
│ │ / ~maya/ │ │ │
│ │ │ DIRECTORIES │ │
│ │ EXPLORE │ / Phlog/ Thoughts on the... │ │
│ │ ? Search │ / gallery/ │ │
│ │ ◊ Discover │ / projects/ Things I'm building │ │
│ │ ≡ Firehose │ │ │
│ │ ◎ Rings │ FILES │ │
│ │ ⊕ Servers │ ¶ about.txt About │ │
│ │ ↻ Random │ ¶ now.txt Now │ │
│ │ │ │ │
│ └─────────────┴────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Status bar: Local · gph:// · burrow v0.9.2 localhost │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
- Language: Swift 5.9+
- Framework: AppKit (not SwiftUI — we need precise text control)
- Text rendering: NSTextView + NSAttributedString
- Fonts: Literata (serif, content) + JetBrains Mono (mono, UI + code)
- Network: Foundation URLSessionStreamTask or NIO for raw TCP
- Storage: UserDefaults (bookmarks, history) + ~/Library/Application Support/Burrow/
- Build: Xcode project or Swift Package Manager
- Target: macOS 13+ (Ventura)
Every response starts with => TYPE followed by optional @ key=value metadata lines, then content.
=> directory
@ accent=#d35400
# ~bruno
ITAD, web craft, en te veel meningen over typografie
/ /~bruno/phlog Phlog/ Thoughts on the web, typography, and digital minimalism 8 items
/ /~bruno/gallery gallery/ 5 items
¶ /~bruno/about.txt about.txt About 313 B
## Neighbors
/ /~maya ~maya Deep Web Craft, Indie Web
Fields are TAB-separated: TYPE\tPATH\tDISPLAY_NAME\tDESCRIPTION\tMETA
Entry types:
/= directory¶= text file→= external link
Sections:
# heading= page title- Lines after heading before first entry = description/subtitle
## Section= section header (Directories, Files, Neighbors, etc.)
=> text
@ words=57 read_min=1 modified=2026-03-17 author=~bruno accent=#d35400
@ ring=Deep Web Craft ring_prev=https://tilde.town/~river ring_next=/~maya
@ inspired_by=/~maya/phlog/post inspired_author=~maya
@ guest_author=~maya
# About
I build things and care about how text looks on screens.
Metadata:
words— word countread_min— estimated reading time in minutesmodified— last modification date (YYYY-MM-DD)author— burrow author (~user)accent— hex color for this burrowring— ring membership (can appear multiple times) withring_prev/ring_nextinspired_by/inspired_author— inspiration creditguest_author— guest post creditseries_current/series_total/series_prev/series_next— series info
Content is raw .gph markup:
# Heading— h1> Quote— blockquote---— horizontal ruleindented— code block (2 spaces)→ https://url— external link/~user/path description— internal link (3 spaces separate path from description)@today— expands to current date (only outside code blocks)- Everything else — paragraph
=> guestbook
@ accent=#d35400
--- Alice · 2026-03-22 14:30
This is great!
--- Bob · 2026-03-21 09:15
Nice site.
Same format as a .gph file but with guestbook entries. Client should render entries + a sign form.
=> bookmarks
@ accent=#d35400
→ https://example.com An interesting site
/~maya/about Maya's about page
Rendered as a list of links with descriptions.
=> search
? Search all burrows
The ? line is a prompt. Client should show a search input field.
=> redirect
/~maya
Client navigates to the target path.
=> error
Not found
Client renders an error message.
=> binary
@ mime=image/png size=45678 path=/~bruno/gallery/photo.png
Binary file. Fetch via HTTPS gateway.
Client can fetch the file via HTTPS as fallback, or display the metadata.
Burrow/
Burrow.xcodeproj
Burrow/
App/
AppDelegate.swift — app lifecycle
MainWindowController.swift — window setup (toolbar, split view)
Views/
SidebarViewController.swift — NSOutlineView with burrows + explore
ContentViewController.swift — NSScrollView + NSTextView for content
AddressBarField.swift — NSTextField subclass for gph:// URLs
Protocol/
GphClient.swift — TCP connection, send request, receive response
GphResponse.swift — parsed response (type, metadata, content)
GphParser.swift — parse raw text into GphResponse
Rendering/
GphRenderer.swift — .gph markup → NSAttributedString
DirectoryRenderer.swift — directory entries → NSAttributedString
GuestbookRenderer.swift — guestbook entries → NSAttributedString
SearchRenderer.swift — search results → NSAttributedString
ThemeManager.swift — colors, fonts, dark mode, seasonal accent
Navigation/
NavigationManager.swift — history stack, back/forward
BookmarkManager.swift — local bookmarks, persistence
Resources/
Literata-Regular.ttf — bundled font
Literata-Italic.ttf
Literata-Medium.ttf
JetBrainsMono-Regular.ttf — bundled font
JetBrainsMono-Medium.ttf
Assets.xcassets — app icon
The client renders .gph into NSAttributedString. No HTML. No WebView.
| Element | Font | Size | Color |
|---|---|---|---|
| Body text | Literata Regular | 16pt | --text |
| Headings | Literata Medium | 22pt | --text |
| Code blocks | JetBrains Mono | 14pt | --text on --faint bg |
| Blockquotes | Literata Italic | 16pt | --muted, left border |
| Links (internal) | JetBrains Mono | 14pt | --accent |
| Links (external) | JetBrains Mono | 14pt | --accent |
| Directory names | JetBrains Mono Medium | 14pt | --text |
| Directory descriptions | JetBrains Mono | 13pt | --muted |
| File sizes / meta | JetBrains Mono | 12pt | --muted |
| Section headers | JetBrains Mono | 10pt uppercase | --muted |
| Sidebar items | JetBrains Mono | 13pt | --muted (--text when active) |
| Status bar | JetBrains Mono | 11pt | --muted |
| Name | Hex |
|---|---|
| --surface | #faf9f7 |
| --text | #1a1a1a |
| --muted | #737373 |
| --faint | #ececea |
| --accent | #1a8a6a (default, overridden by burrow accent) |
| Name | Hex |
|---|---|
| --surface | #161614 |
| --text | #e8e6e1 |
| --muted | #8a8a8a |
| --faint | #222220 |
| --accent | #3ab89a |
| Season | Months | Color |
|---|---|---|
| Spring | Mar-May | #2d8a4e (green) |
| Summer | Jun-Aug | #c4841d (gold) |
| Autumn | Sep-Nov | #a0522d (brown) |
| Winter | Dec-Feb | #4a7ab5 (blue) |
For each line in the .gph content:
# Heading→ NSAttributedString with heading font, extra spacing above> Quote→ indented paragraph with left border (via NSParagraphStyle), italic---→ horizontal line (draw rect in NSTextView or use attachment)code line→ monospace font, faint background (via NSParagraphStyle backgroundColor)→ https://url→ clickable link with → prefix, accent color/~user/path desc→ clickable internal link with / prefix, accent color@today→ expand to YYYY-MM-DD (only outside code blocks)- Empty line → paragraph spacing
- Everything else → body text paragraph in Literata
Links are clickable via NSTextView delegate (textView(_:clickedOnLink:at:)).
Parse TAB-separated entries. Render as:
- Section headers (## DIRECTORIES, ## FILES) in small caps
- Each entry: icon + name + description + meta, properly aligned
/entries: accent-colored/, bold name¶entries: muted¶, regular name→entries: accent-colored→, link
Above the content, show:
- Breadcrumbs:
localhost / ~bruno / about - If inspired_by: "Inspired by ~maya" with link
- If guest_author: "Guest post by ~maya" with link
- Reading time: "~1 min read · 57 words"
- Last modified: "Modified: 2026-03-17"
Below the content, show:
- Series navigation: "← Part 2 · Part 3 of 5 · Part 4 →"
- Ring navigation: "← Previous · Ring Name · Next →"
Items:
- Back button (NSToolbarItem) — standard back arrow, Cmd+[
- Forward button (NSToolbarItem) — standard forward arrow, Cmd+]
- Home button (NSToolbarItem) — house icon, Cmd+Shift+H
- Address bar (NSToolbarItem with NSTextField) — gph:// URL, Cmd+L to focus
- Bookmarks button (NSToolbarItem) — star icon, Cmd+B
- Left: sidebar (220pt, collapsible)
- Right: content area (flexible)
Two sections:
- BURROWS — fetched from root directory listing, shows ~user/ entries
- EXPLORE — hardcoded: Search, Discover, Firehose, Rings, Servers, Random
Each item has an icon (the gph type symbol) and a label. Clicking navigates.
- Read-only NSTextView
- Literata font for prose, JetBrains Mono for code/UI
- Links are clickable (delegate handles navigation)
- Scroll position resets to top on navigation
- No editing capability
Shows: Local · gph:// · burrow v0.9.2 on the left, hostname on the right.
| Shortcut | Action |
|---|---|
| Cmd+[ | Back |
| Cmd+] | Forward |
| Cmd+L | Focus address bar |
| Cmd+Shift+H | Home |
| Cmd+B | Toggle bookmarks panel |
| Cmd+F | Find in page (NSTextFinder) |
| Cmd+D | Bookmark current page |
| Cmd+R | Reload current page |
| Escape | Clear address bar / close search |
| Space | Page down (standard NSTextView) |
| Shift+Space | Page up |
- User types URL in address bar → press Enter
NavigationManager.navigate(url)calledGphClient.fetch(url)— opens TCP socket, sendsurl\r\n, reads until EOFGphParser.parse(response)— returnsGphResponsewith type, metadata, content- Based on response type:
directory→DirectoryRenderer.render(response)→ NSAttributedStringtext→GphRenderer.render(response)→ NSAttributedStringguestbook→GuestbookRenderer.render(response)→ NSAttributedStringsearch→ show search prompt orSearchRenderer.render(response)redirect→NavigationManager.navigate(target)error→ show error view
- Set NSTextView.textStorage to the rendered NSAttributedString
- Update address bar, breadcrumbs, sidebar active state
- Push URL to history stack
The gph:// protocol is already functional. No structural changes needed.
Currently guestbook signing uses ?name=X&message=Y in the URL. This works but is inelegant. Consider a proper write protocol:
gph://localhost/~bruno/guestbook?name=Alice&message=Hello
This already works — the server parses query parameters. No change needed.
Already works: gph://localhost/search?q=typography. No change needed.
The server currently returns metadata only for binary files over gph://. For the native client, we could:
- Stream raw bytes after the header (client detects binary type and handles accordingly)
- Or keep the HTTPS fallback for images
Decision: HTTPS fallback for now. Binary rendering in NSTextView via NSTextAttachment is possible but can wait for v2.
The ?slow=1 parameter is HTTP-specific. For the native client, this should be a client-side preference (bigger font size in NSTextView). No server change needed.
The server already counts page loads per burrow for HTTP requests. Add the same counter increment for gph:// requests. Small server change needed.
- Create Xcode project with AppDelegate
- Set up main window with NSToolbar (back, forward, home, address bar)
- Set up NSSplitView (sidebar + content)
- Implement GphClient — TCP connect, send, receive, parse
- Test: connect to localhost:1970, display raw response in NSTextView
- Implement GphRenderer — .gph markup → NSAttributedString
- Implement DirectoryRenderer — directory entries → NSAttributedString
- Bundle Literata + JetBrains Mono fonts
- Implement ThemeManager — light/dark mode, accent colors
- Test: navigate to ~bruno/about, verify typography matches browser
- Implement NavigationManager — history, back/forward
- Wire address bar to navigation
- Wire sidebar clicks to navigation
- Wire content links to navigation (NSTextView delegate)
- Implement redirect handling
- Test: full navigation flow — home → ~bruno → phlog → post → back → forward
- Implement sidebar — fetch burrows from root, hardcode explore items
- Implement search — show NSTextField prompt, submit query, render results
- Implement guestbook view + sign form
- Implement bookmarks — Cmd+D to add, Cmd+B to show panel
- Implement status bar
- Test: all features match browser functionality
- Seasonal accent colors
- Inspired-by rendering
- Guest author rendering
- Series navigation
- Ring navigation bars
- Reading time + word count display
- Last-modified display
- Breadcrumbs
- Dark mode support
- Error pages
- Connection timeout handling
- App icon
- Test: side-by-side with browser, verify visual parity
- Zero WebView — all rendering via NSTextView + NSAttributedString
- Zero HTML — no HTML generated or parsed anywhere
- Zero JavaScript — obviously
- Native macOS look — NSToolbar, NSSplitView, standard keyboard shortcuts
- Visual parity — content typography matches the HTTPS gateway (same fonts, same sizes, same colors)
- Full protocol support — all 8 response types handled
- Navigation works — address bar, sidebar, content links, back/forward, home
- Offline graceful — timeout errors shown, app doesn't hang
- Dark mode — follows system preference
- Accent colors — per-burrow accent from server metadata