Skip to content

PR #3: Phase 4 apps — 7 of 8 apps + Spotify helper (Music pending) - #3

Merged
radroid merged 33 commits into
feat/mac-os-1984-desktopfrom
feat/desktop-apps
Apr 16, 2026
Merged

PR #3: Phase 4 apps — 7 of 8 apps + Spotify helper (Music pending)#3
radroid merged 33 commits into
feat/mac-os-1984-desktopfrom
feat/desktop-apps

Conversation

@radroid

@radroid radroid commented Apr 16, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 4 rollout. Single branch, per-app commits. Ships 7 of 8 apps now; Music lands in a follow-up commit once the Spotify refresh token is captured.

Commits (in order)

  1. Refine DESKTOP-PLAN — switch from per-app PRs to single PR + per-commit strategy; document Spotify refresh-token architecture and per-environment env var setup
  2. scripts/spotify-oauth.mjs — one-shot OAuth helper for capturing the refresh token locally (never commits secrets)
  3. App: Calculator — Mac OS 1 grid calculator, left-to-right chain eval, keyboard support
  4. App: Note Pad — torn top edge + lined paper + localStorage auto-save
  5. App: Control Panel — user card + live clock + system diagnostics + battery
  6. App: Finder (Documents) — mirrors public/ with Mac-styled folder names, preview overlay per file type, right-click Open/Download
  7. Polish: +10% text/icon scale — shell + all four committed apps, from live review
  8. App: Scrapbook (Journal) — paged blog reader, arrow nav, keyboard
  9. Finder folder icon swap — use user-provided public/app-icons/folder.svg
  10. Window title bar v1 — inset focus-state horizontal lines, clean gap around close box
  11. Scrapbook blog year fix — 2025 → 2026 in both the inlined data and CONTENT-ARCHIVE.md
  12. Polish: larger Control Panel avatar + tighter title-bar stripe inset (v2)
  13. Window title bar v3 — stripes reach window edges with close box sitting on them (matches reference)
  14. App: Curly Browser — no-iframe bookmarks launcher, 11 tiles, tan info banner explaining the quirk, Shadcn right-click menu
  15. App: World Map — react-simple-maps + world-atlas TopoJSON, 24 visited countries with 4×4 Mac 1 checkerboard dither pattern fill, hover tooltip, legend

Still to land

  • Music — Spotify now-playing with vinyl UI. Gated on the one-time OAuth helper run (node scripts/spotify-oauth.mjs) to capture SPOTIFY_REFRESH_TOKEN into .env.local.

Test plan

  • Boot → desktop transition still works
  • All 7 apps open from their desktop icons and from right-click Open
  • Focused window title bar shows the stripe pattern, unfocused is plain
  • Calculator: chain ops, divide-by-zero, C clear
  • Note Pad: type, reload, content persists
  • Control Panel: live clock ticks, battery reflects system
  • Finder: navigate folders, double-click file → preview, right-click Download
  • Scrapbook: ◀ / ▶ arrows navigate between posts, counter updates
  • Browser: click a bookmark → opens in real new tab, right-click menu Copy Link works
  • World Map: hover on visited country → tooltip with name, legend visible

Deploy notes for Spotify (when Music lands)

Set these three env vars in both Cloudflare Pages Preview and Production scopes:

  • SPOTIFY_CLIENT_ID
  • SPOTIFY_CLIENT_SECRET
  • SPOTIFY_REFRESH_TOKEN

The backend /api/spotify/now-playing route uses the server-side refresh-token grant, so the same code works on localhost, every preview *.pages.dev URL, and production — no redirect URI needed at runtime.

🤖 Generated with Claude Code

radroid and others added 17 commits April 15, 2026 20:14
Replace the original parallel-worktree-per-app strategy with a single
branch (feat/desktop-apps → feat/mac-os-1984-desktop) containing one
commit per app. The previous plan assumed subagents could run git/tsc/
gh inside isolated worktrees; in practice the session sandbox denies
Bash for subagents, so the main agent drives every git operation while
subagents write component files only.

Also document the Spotify integration architecture in App 8: refresh-
token grant for runtime (no per-URL redirect registration), one-time
OAuth helper on 127.0.0.1:8888/callback for dev-time capture, and a
per-environment env-var table (.env.local / Cloudflare Preview /
Production) so the same route.ts works everywhere.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node 18+ ESM helper. Reads SPOTIFY_CLIENT_ID/SECRET from .env.local,
starts a loopback server on 127.0.0.1:8888, opens the browser to
Spotify's authorize endpoint, and on /callback exchanges the auth
code for a refresh token. Appends SPOTIFY_REFRESH_TOKEN to .env.local
if not already present.

The callback URL is used only for this one-time ritual — at runtime
the Music app uses the refresh token server-side via the refresh_token
grant, so no redirect URI registration is needed for Cloudflare preview
or production deploys.

Run: node scripts/spotify-oauth.mjs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four-column grid with C / ÷ × − + operators, = spanning two rows on
the right, left-to-right chain evaluation matching the original Mac
calculator (2 + 3 * 4 = 20), division-by-zero → "Error", keyboard
shortcuts, and authentic 1-bit black/white button invert on :active.
No chrome, lives inside the generic Window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline SVG zigzag for the torn top edge (stretches across any width
via viewBox + preserveAspectRatio), repeating-linear-gradient for the
lined paper background, and an auto-saving textarea persisted to
localStorage key `curly-os-notepad`. Pre-populates with a welcome
note on first load; empty string is respected once the user clears.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stacked panel sections: user card (avatar, name, role, location,
contact links), live clock updating every second, system diagnostics
(browser, OS, screen resolution, window size, language, connection
type, timezone), Battery Manager readings with charging/levelchange
listeners, and derived location from Intl.DateTimeFormat timezone.
All runtime data comes from browser APIs — no network calls. Battery
section degrades gracefully on Firefox/Safari where the API is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Classic icon grid with folder navigation (path stack + Back button),
Mac-styled labels (Applications ← app-icons, Documents ← cv, Fonts,
Developer ← tech-icons), and an internal status bar that stays in
sync with the current folder. Double-click opens a file preview
overlay inside the Finder window: images/SVGs via <img>, audio via
<audio>, PDFs via <iframe> (browser native viewer), fonts show a
sample, unknowns fall back to "not available". Right-click provides
Open, Download, Get Info (disabled), Rename (disabled) via the
existing Shadcn ContextMenu.

Also hoists FINDER_DEFAULT_MENUS above APP_REGISTRY so the finder
entry can reference it without a temporal-dead-zone error, and drops
the unused `statusBar` fallback since the component renders its own.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User feedback on the first live screenshot was that the content was
hard to read at the default sizing. Bumps all shell and app font
sizes up one Chicago-friendly step (~10%):

  shell
  - desktop-icon: label 10→11 / 12→13, icon image 30→33 / 44→48,
    tile width 82→90 / 96→106, gaps 2→3 / 4→5
  - window title bar: 11→12, status bar: 10→11
  - menu bar default fontSize 12→13, height 22→24

  apps
  - calculator: button 13→14, display 20→22
  - note-pad: textarea 13→14, line-height 20→22, lined-paper
    gradient bumped to match
  - control-panel: section-header / label / value 11→12,
    user name 13→14, link buttons 10→11
  - finder: folder/file labels 12→13, secondary 11→12, heading
    18→20, status bar 10→11, breadcrumb 13→14

Stays inside the 1-bit Chicago aesthetic — no hierarchy changes,
just a single step up on every hard-coded px size.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Classic Mac Scrapbook-style paged viewer. Extracted the 3 blog posts
from CONTENT-ARCHIVE.md as inline data (no markdown renderer added —
body is rendered via <pre style="white-space:pre-wrap"> for a period-
accurate look). Nav footer with ◀ / ▶ arrows disabled at the ends, a
"1 / N" page counter, and Left/Right keyboard navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the inline-drawn folder SVG with the user-provided 1-bit
folder asset. Keeps the Mac 1 aesthetic consistent with the rest of
the app icons and fixes the earlier too-cartoony rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The active title bar's horizontal-stripes pattern used to go
edge-to-edge and touched the close box, making focused windows look
crowded and unlike the classic Mac OS 1 reference. Restructure the
title bar into an outer white wrapper with a 2px inset and an inner
row that carries the stripes; the close box and title text sit in
their own white-padded spans so the stripes never crowd them, and
the right side mirrors the close-box cluster width so the title
stays visually centered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The three blog posts were originally mis-dated on the previous
website — they should all be 2026. Fixes the inlined Scrapbook data
and the source CONTENT-ARCHIVE.md so anything pulling from it in the
future picks up the right year.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Control Panel avatar 56→84px so the user card reads at a glance
- Title bar stripes now run edge-to-edge horizontally (removed the
  outer left/right padding) so they meet the window border like the
  Mac OS 1 reference. Close box has 4px of white against the window
  edge and 6px against the stripes; title text has 10px cushion each
  side. Right side mirrors the close-box footprint to keep the title
  visually centered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Marks commits 1-6 (plan, Spotify helper, Calculator, Note Pad, Control
Panel, Finder) as done, logs the extra commits surfaced from reviewing
the live preview (font/icon polish, folder icon swap, title-bar stripe
fix, blog year correction, avatar bump), and leaves Browser / World
Map / Music as the remaining work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous fix still had a white cushion span wrapping the close box,
which created a solid white block from the window edge inward before
the stripes began. In the Mac OS 1 reference, the stripes fill the
title bar edge-to-edge horizontally and the close box is an opaque
white element sitting ON the stripes, with only a tiny padding inset
(4px) from the window border.

This rewire puts the padding on the stripe container itself — since
background-image extends through padding, stripes fill the 4px inset
too, and the close box + its transparent right-side mirror sit as
opaque / transparent elements on that background. The title text
still cuts a white hole via its own background.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No iframes. Only view is a bookmarks home with 11 tiles: 8 projects
(Penguin Mail, ARK Experience, Bridger, Stella 56 Diamonds, Playground,
Couples Budget, 75 Creates, KayVee Gems) + 3 tools (Google, Claude,
ChatGPT). Click opens in a new real-browser tab via window.open;
right-click exposes Open in New Tab + Copy Link via the existing
Shadcn ContextMenu. Disabled Back/Forward buttons and a decorative
read-only address bar match the Mac 1 chrome. Tan info banner below
the toolbar explains the "iframes get blocked" quirk up front.
Favicons pulled from Google's S2 service and pixelated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses react-simple-maps + world-atlas (TopoJSON via jsdelivr CDN) to
render the Natural Earth country borders. Visited countries match
against geo.properties.name (with both common name variants for the
USA and Czechia to cover either world-atlas naming convention).

Visited fill is a 4×4 checkerboard SVG <pattern> (two 1×1 black
rects) — the canonical Mac OS 1 50% stipple, matching the worldmap
reference aesthetic. A hidden 0×0 <svg><defs> hosts a second pattern
id so the bottom-right legend swatch (which lives in a <div> outside
the map's SVG) can resolve its url(#id) fill. Hover tooltip follows
the cursor via position: fixed (survives the window's transform
container in maximized mode).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
raj-portfolio 6bb4740 Commit Preview URL

Branch Preview URL
Apr 16 2026, 01:37 AM

radroid and others added 11 commits April 15, 2026 20:45
Hovering a visited country now shows an inverted tooltip — black
background, white text, with a ✓ prefix — so the "you've been there"
status is instantly visible without having to match the dither fill
against the legend. Unvisited countries keep the plain white tooltip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drops the bottom-right legend (and the hidden legend-pattern SVG that
supported it) to reclaim that space. Switches from geoMercator to
geoEqualEarth which has a more compact aspect ratio — no wasted
polar stretch — and bumps the scale to 175 plus a small center shift
([10, 15]) so the populated latitudes fill the window. Sets
width/height to 800×400 so the viewBox matches the compact
projection shape instead of the default 800×600 which left ~200px
of empty vertical space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from geoEqualEarth (curved edges) to geoEquirectangular — the
plate carrée projection — so latitude and longitude are evenly spaced
straight lines and country borders read cleanly without curvature.
Scale 128 fills the 800×400 viewBox exactly (2:1 aspect matches the
projection's native ratio), and center [0, 0] keeps the full latitude
range from 90N to 90S in frame so Antarctica is always visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ottom

The previous height left ~90px of empty space below the Equirectangular
map in the default window. Recompute the height clamp from the width
using the map's native 2:1 content ratio: content height = width × 0.5
(= 35cqw), plus 21px for the title bar. Max clamps at 291px (for the
540px max width), min at 161px.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Visited count label moves from top-left to bottom-left so it doesn't
  sit on top of Canada / the Arctic region.
- Adds .dev.vars and .dev.vars.* to .gitignore so the local wrangler
  secrets file (mirrors Cloudflare Pages env vars for bun run preview)
  stays out of git.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents country borders in the map from bleeding through the text
and making the count hard to read. Matches the styling pattern used
elsewhere in the OS — plain white panel with a 1px black border.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the Music app and the /api/spotify/now-playing route that powers
it. Runtime architecture: server-side refresh-token grant exchanges
SPOTIFY_REFRESH_TOKEN → access_token on each invocation, then fetches
currently-playing + recently-played from the Spotify Web API. Returns
a normalized { isPlaying, track, recent } shape; every failure path
returns HTTP 200 with an error field so the UI can show an offline
vinyl instead of breaking the desktop.

UI:
- Left column: vinyl record — spinning when isPlaying, paused otherwise,
  with the current album art clipped to a circle as the label. Status
  pill above ("Now Playing" / "Last Played") + track/artist + a subtle
  2px progress bar during active playback.
- Right column: Recently Played list with small album art thumbnails
  and track/artist rows.
- Offline state: solid white "Offline" vinyl + "Not connected to
  Spotify" caption, no broken UI.
- prefers-reduced-motion: vinyl freezes instantly.

Polls /api/spotify/now-playing every 10s; client-side caching disabled
via cache: 'no-store'.

Widens the Music window defaultSize from the original small/medium
clamp to clamp(320px, 60cqw, 480px) × clamp(220px, 55cqh, 340px) so
the two-column vinyl+playlist layout has room to breathe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ternal

- New 16×16 1-bit SVGs for Back / Forward / Home in public/browser-icons/
  replace the old Unicode glyph buttons in the toolbar.
- Add per-bookmark openMode: Projects keep opening in a real browser tab
  (iframes would be blocked anyway); Tools (Google / Claude / ChatGPT)
  now embed inside the Curly Browser window via an internal view state.
- Embed view renders an <iframe sandbox="..."> and a persistent yellow
  footer banner with an "Open in real browser →" escape button, since
  Google / Claude / ChatGPT all set frame-ancestors and the iframe is
  very likely to come up blank.
- Back and Home buttons both return to the bookmarks page when in the
  embed view; they stay disabled on the home page. Forward remains
  decorative for now.
- Address bar reflects the current view URL, read-only.
- Right-click context menu keeps "Open in New Tab" as an always-external
  action regardless of the bookmark's default openMode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Window default size goes from 320-480 × 220-340 up to 440-640 × 300-440
so the vinyl + playlist layout has room to breathe. Internal content
scales up with it:
- Vinyl 150→210px, label 66→96px, hole 10→14px, groove rings rescaled
- Left column flex 50→55%, padding 12→18px, status label 9→11px
- Track title 12→15px, artist 11→13px
- Recently-played header 10→12px; each row: thumb 30→44px, padding
  4→7px, track name 11→13px, artist 10→11px

Also threads spotifyUrl through the API route (from
item.external_urls.spotify in both currently-playing and recently-
played responses) and uses it to:
- Wrap each row in the Recently Played list in an <a target="_blank">
  that opens the track in Spotify, with a soft hover highlight
- Wrap the main vinyl + album art label in the same link when a URL
  is present

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small touch-up from the user-provided browser.svg asset.
All 8 apps are now on feat/desktop-apps with their iterative live-review
polish commits logged. Next: review → merge PR #3 into
feat/mac-os-1984-desktop, then final integration pass before merging to
main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@radroid radroid self-assigned this Apr 16, 2026
radroid and others added 5 commits April 15, 2026 21:15
The list was getting stuck on a stale copy because the response was
being cached somewhere between Spotify and the browser — likely the
Cloudflare edge, since Spotify's recently-played endpoint itself
changes whenever new tracks play.

Three layers of no-cache:

1. Route-level: add `revalidate = 0` and `fetchCache = 'force-no-store'`
   alongside the existing `dynamic = 'force-dynamic'`. Belt and braces.
2. Response headers: every Response.json now goes through a `json()`
   helper that attaches `Cache-Control`, `CDN-Cache-Control`, and
   `Cloudflare-CDN-Cache-Control` = `no-store`. Cloudflare's edge
   respects the CDN-* headers specifically.
3. Client fetch: append `?t=${Date.now()}` to the request URL and pass
   `Cache-Control: no-cache` — so even if a downstream layer keyed on
   URL+Vary, every poll has a unique URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clamp bumps: width 280–540 → 560–1080, height 161–291 → 301–561. Fluid
middle moves from 70cqw to 90cqw so the window fills more of the CRT
screen in both normal and maximized modes. Height formula stays in
sync with the 2:1 map aspect (45cqw + 21px) so Antarctica still lines
up exactly with the bottom of the window.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- API route fetches /me/top/artists with time_range=short_term (~4
  weeks) instead of medium_term (~6 months).
- Aggregates genres from the top 50 artists and deduplicates near-
  duplicates (e.g. "afrobeats" vs "afrobeat" merged by stripping
  trailing "s" and combining counts). Returns top 10 genres + top 10
  artists (name, image, spotifyUrl) alongside the currently-playing
  track.
- Right panel now shows two scrollable sections: "Top Genres" with
  bar charts + "Top Artists" with circular thumbnails linked to their
  Spotify pages.
- OAuth helper updated with user-top-read scope (user must re-run
  once to capture a new refresh token).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Route: Array.from() on Map iterators (no downlevelIteration flag),
  type lastTrack as `any` so the later assignment satisfies
  strictNullChecks.
- Component: remove flex:1 + overflowY from GenreList so both sections
  render at their natural height and the parent column scrolls through
  them. Previously GenreList's flex:1 hogged all vertical space,
  leaving artists barely visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Marks every Phase 4 commit as done including the live-review polish
(cache-busting, world map 2× window, genres+artists panel, overlap
fix). Updates the Readiness Checklist: Phase 4 section is closed,
Cloudflare Pages env vars confirmed set, Phase 5 integration items
are the remaining work before merging to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@radroid
radroid merged commit fc65c9e into feat/mac-os-1984-desktop Apr 16, 2026
1 check passed
radroid added a commit that referenced this pull request Apr 17, 2026
The PR #3 workflow steps and "Why This Strategy" rationale were
execution notes for completed work. Branch tree retained as the
canonical merge path.
@radroid
radroid deleted the feat/desktop-apps branch April 27, 2026 22:17
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