Borderland is a desktop-first writing companion (with a web build) that helps you Ease Out at the end of a session—capturing what was alive, what’s next, and how it felt—then Drop In at the start of the next session with that context surfaced as a simple slide flow. North Star holds your through-line, and Journal / Progress views surface history and “written into existence” notes.
| Layer | Technology |
|---|---|
| UI | React 19, TypeScript |
| Bundler / dev server | Vite 7 (@vitejs/plugin-react) |
| Desktop shell | Tauri v2 (Rust, tauri-plugin-deep-link, tauri-plugin-opener, tray) |
| Persistence | Browser localStorage (no backend) |
https://github.com/gavinocarroll/borderland
| Branch | Role |
|---|---|
main |
Stable v0.1 line |
figma-redesign |
Current active development (UI/flows, web parity, deployment config) |
Default work happens on figma-redesign; merge to main when you cut a stable release.
- The repo is intended to deploy from Vercel (static Vite build).
vercel.jsonskips Vercel builds whenVERCEL_GIT_COMMIT_REFisgh-pages(avoids noisy double-builds from that branch).vite.config.tssetsbase: process.env.VITE_BASE_URL ?? "/"— setVITE_BASE_URLin Vercel if the app is served from a subpath (include trailing slash if required for your setup).
Production URL (typical): https://borderland.vercel.app — confirm under Vercel → your project → Domains; if the project slug differs, the hostname will differ.
Preview URLs: Each push to a connected branch gets a unique *.vercel.app preview URL on the deployment summary.
Routing is not URL-based in-app (no React Router). src/App.tsx holds activeView and renders one screen. Web uses location.hash on load; macOS app uses borderland:// deep links (Tauri emits a deep-link event).
activeView |
Component | Role / UI label (typical) |
|---|---|---|
welcome |
WelcomeScreen.tsx |
First-run / onboarding entry |
north-star-entry |
NorthStarEntry.tsx |
Capture or edit North Star (entry flow) |
north-star-log |
NorthStarLog.tsx |
North Star log / history |
ease-out-form |
EaseOutForm.tsx |
Ease Out multi-step form |
drop-in |
DropInView.tsx |
Drop In slide replay + “time to write” |
progress |
ProgressView.tsx |
Journal — session history (nav centre label) |
progress-v1 |
ProgressViewV1.tsx |
Progress — “things written into existence” (progress_marker) |
timer |
TimerScreen.tsx |
Timer with instruction passed from Drop In |
Hash routing (web only, read once on load):
| Hash | activeView |
|---|---|
#dropin |
drop-in |
#easeout |
ease-out-form |
#northstar |
north-star-log |
#journal |
progress |
#progress |
progress-v1 |
| Field | Type | Purpose |
|---|---|---|
id |
string |
Unique session id |
createdAt |
string |
ISO timestamp (session created / Ease Out saved) |
writingStartedAt |
string? |
When user logged “start writing” from Drop In |
easeOutSubmittedAt |
string? |
Reserved for telemetry |
dropInViewedAt |
string? |
When Drop In was opened for this session |
sessionClosedAt |
string? |
Reserved for telemetry |
easeOut |
PromptResponse[] |
Answers keyed by promptId |
| Field | Type | Purpose |
|---|---|---|
promptId |
string |
e.g. lines_alive, continue_from, felt_sense, progress_marker |
entries |
string[] |
One or more text lines per prompt |
timeSpentMs |
number? |
Optional per-prompt timing |
| Field | Type | Purpose |
|---|---|---|
hasCompletedFirstSession |
boolean |
Skips Welcome after first Ease Out completion |
userName, userEmail, projectTitle |
string? |
Profile / project labelling |
northStar |
string? |
Current North Star text |
northStarHistory |
NorthStarEntry[]? |
Append-only style history |
dropInCount |
number? |
Counts Drop In opens (e.g. North Star slide every 3rd) |
| Field | Type |
|---|---|
value |
string |
updatedAt |
string (ISO) |
PromptConfig in the same file drives src/config/prompts.ts (labels, Drop In labels, which prompts appear in Ease Out vs Drop In).
| Key | Contents |
|---|---|
borderland_sessions |
JSON array of Session |
borderland_meta |
JSON object AppMeta |
| Function | Behaviour |
|---|---|
getSessions() |
Parse borderland_sessions; invalid → [] |
appendSession(session) |
Push session, write array back |
getLastSession() |
Last element of array or null |
updateLatestSession(patch) |
Merge patch into last session; no-op if empty |
getMeta() |
Parse borderland_meta; returns defaults if missing/invalid |
setMeta(meta) |
Stringify and save full AppMeta |
saveNorthStar(value) |
Updates northStar and appends NorthStarEntry to northStarHistory |
Known gap: getMeta() rebuilds a subset of fields and does not currently merge optional fields like dropInCount from stored JSON (see Known issues).
All use e.metaKey ( ⌘ Command on macOS ). Behaviour is global while the window has focus.
| Shortcut | Action |
|---|---|
| ⌘ J | Open Journal (progress → ProgressView) |
| ⌘ K | Open North Star log (north-star-log) |
| ⌘ P | Open Progress v1 (progress-v1 → ProgressViewV1) |
| ⌘ ⇧ X | Export sessions + meta + exportedAt as JSON to clipboard (textarea + document.execCommand('copy')), then alert |
| ⌘ ⇧ Backspace | localStorage.clear() and return to Welcome |
Drop In (DropInView.tsx): ← / → arrow keys step slides (in addition to click-to-advance), with cleanup in the same effect as the click listener.
Registered scheme: borderland:// (src-tauri/tauri.conf.json, tauri-plugin-deep-link). Rust (src-tauri/src/main.rs) shows/focuses the window and emits payload to the webview; React listens only when __TAURI_INTERNALS__ is on window.
| URL prefix | Screen |
|---|---|
borderland://dropin |
Drop In |
borderland://easeout |
Ease Out |
borderland://northstar |
North Star log |
borderland://journal |
Journal (progress) |
Note: #progress (web) maps to progress-v1; there is no borderland://progress handler in App.tsx today—add one if you want parity.
Use the same destinations with hash fragments after your deployed origin, e.g. https://borderland.vercel.app/#dropin.
Copy link in Drop In: Tauri copies borderland://… lines; browser copies origin + pathname + #… (see handleCopyLink in DropInView.tsx).
cd borderland
npm install
npm run devVite serves on port 1420 (matches Tauri devUrl in tauri.conf.json). Open the printed local URL in a browser for web-only dev.
With Tauri window:
npm run tauri devPrerequisites: Rust toolchain, Xcode command line tools, and Tauri prerequisites for macOS.
cd borderland
npm install
npm run tauri buildArtifacts (exact subfolders can vary slightly by Tauri 2 / bundle settings) appear under:
src-tauri/target/release/bundle/ — look for dmg/ (.dmg installer) and macos/ (.app).
tauri.conf.json sets "targets": "all"; DMG is included for macOS when building on macOS.
-
Connect the repo once in Vercel: New Project → import
gavinocarroll/borderland, framework Vite, build commandnpm run build, outputdist. -
Set branch: Production can track
mainorfigma-redesigndepending on your release process; previews build for other connected branches. -
Push:
git checkout figma-redesign # or main git add -A git commit -m "your message" git push origin figma-redesign
-
Vercel runs
npm run buildon the push; inspect the deployment log for the live Production or Preview URL.
vercel.json: Pushes that only touch gh-pages are ignored by Vercel’s build (script exits 0 → skip), so that branch does not spawn app deploys.
getMeta()vsAppMeta: Reconstructed meta omits some stored optional fields (e.g.dropInCount) so behaviour after a full reload may not match in-memory state untilgetMetareturns a full merge ofAppMeta.- Global
console.logon every keydown inApp.tsx— debugging noise; remove or guard withimport.meta.env.DEV. - Telemetry export uses deprecated
document.execCommand('copy')— fragile under strict permissions; considernavigator.clipboardwith fallback. progressvsprogress-v1: Two different “progress” experiences (Journal vs Signs of progress); naming and shortcuts (⌘Jvs⌘P) take discipline to document for users.- Deep link parity: Web
#progresshas no matchingborderland://…handler inApp.tsxyet. - CSP is
nullintauri.conf.json— tighten before shipping untrusted remote content.
src/App.tsx # activeView, hash, deep-link listener, shortcuts
src/storage/storage.ts # localStorage API
src/types/index.ts # Session, AppMeta, NorthStarEntry, PromptConfig
src/config/prompts.ts # Prompt catalogue
src/views/*.tsx # Screens
src-tauri/ # Rust, Tauri config, icons, Info.plist
vercel.json # Vercel ignore rules for gh-pages
vite.config.ts # base path, port 1420, Tauri HMR host
package.json marks the package as private; add a license file when you open-source or distribute.