Refactor desktop codebase to reduce LOC and dead code - #4
Open
radroid wants to merge 221 commits into
Open
Conversation
…lans CLAUDE.md: correct boot description, BootSequence→BootScreen component name, and .woff2→.woff font path. Add pointer to DESKTOP-PLAN.md. DESKTOP-PLAN.md: fix container-type (inline-size→size for cqh support), correct Phase 4 branching (apps branch from mac-os-1984-desktop, not pre-app-foundation), fix branch tree diagram, drop redundant isSelected from WindowState, collapse isMaximized two-step into direct context usage. MOBILE-PLAN.md: initial planning document for iPhone OS 1 mobile experience. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mobile gate - Window positions stored as container percentages (0–1) instead of pixels so they scale automatically on maximize toggle - isMaximized state lifted from WindowManager context to page.tsx, passed as props to IMacG3Frame and Desktop - Maximize trigger simplified to chin button only (removed menu bar option) - Clarified mobile never advances past welcome phase - Drag listeners attached to document to prevent fast-drag cursor loss Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POSTHOG-RESEARCH.md: architectural reference notes on PostHog.com's desktop-OS implementation (state model, window chrome, desktop shell, styling primitives). Compiled via parallel subagent exploration of the posthog.com repo. DESKTOP-PLAN.md updates inspired by the research: - Derived focused window (no stored activeWindowId) — top of z-stack IS the focused window via useMemo - Contiguous zIndex reshuffle on focusApp (no counter drift) - 5px click-vs-drag threshold on draggable title bars - Lazy content mount after window entry animation - Zoom-from-origin open animation using clicked-icon Rect - Cascade-on-open position spec and close-normalizes-zIndex rule - Menu bar z-index sits above window stack - Reduced-motion covers maximize + open + close animations - Inspiration & Anti-patterns section (7 patterns adopted, 9 rejected, 4 future enhancements) - Readiness Checklist marking PR #1 ready and app content blockers CLAUDE.md: note that dev server is always running and should not be started by automation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the `'desktop'` screen phase and maximize mode as the foundation for the interactive Mac OS 1984 desktop experience. Screen phases: - ScreenPhase gains `'desktop'` - Welcome screen renders without its menu bar (menu bar moves to the desktop phase in Phase 2+3) - Desktop viewports auto-advance from `'welcome'` → `'desktop'` after a brief hold; mobile stays on welcome permanently Maximize mode: - `isMaximized` state lifted to `page.tsx`, passed to IMacG3Frame and CRTScreen as props (per DESKTOP-PLAN architecture) - Chin button on the iMac frame toggles maximize; a floating Restore button appears when maximized so the control stays reachable after the chin hides - IMacG3Frame hides body/chin/stand when maximized; CRTScreen expands to fill the viewport - CRTScreen gains `container-type: size` to enable cqw/cqh-based window sizing in later phases - All transitions respect prefers-reduced-motion Containing-block fix: - The initial intro scale animation is released after 700ms (new `introDone` state), so a persistent `transform: scale(1)` no longer creates a containing block that traps the maximized IMacG3Frame's `position: fixed, inset: 0`. Assets (user-provided): - public/app-icons/: 1-bit SVGs for calculator, control-panel, finder, journal, music, notepad, trash, world-map (browser icon pending) - public/apple-icon.svg - public/cv/Raj_Dholakia_Resume_FullStack.pdf Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the full interactive desktop — draggable windows,
placeholder apps, icon grid, and a dynamic menu bar that adapts to
the focused app. This is the last piece of the foundation PR before
app PRs can branch off in parallel.
Window manager (app/components/desktop/window-manager.tsx)
- React Context holding a windows record, selectedIconId, and an
hasOpenedAnyApp flag
- Derived activeWindowId via useMemo — top of z-stack is the focused
window; no separate focus field to sync
- focusApp does a contiguous zIndex reshuffle (no counter drift)
- closeApp re-normalizes zIndex so gaps never appear
- Cascade-on-open: first window at {12%, 14%}, +3% per next, wraps
back to base past {60%, 55%}
- moveWindow clears fromOrigin so the zoom animation can't replay
App registry (app/components/desktop/app-registry.tsx)
- Typed AppDefinition/MenuConfig/MenuItem/Rect
- All 8 apps registered with the user-provided 1-bit SVG icons
(browser.svg now included)
- clamp() + cqw/cqh sizes per the DESKTOP-PLAN
- Shared ComingSoon placeholder component until Phase 4 replaces it
- FINDER_DEFAULT_MENUS exported for the fallback menu bar state
Window component (app/components/desktop/window.tsx)
- Title bar with striped-active pattern, close box with an inline-SVG
X, focus-on-click
- Drag uses direct DOM transform: translate3d() during the move,
rAF-throttled; only the final position is committed via moveWindow
on mouseup. Avoids re-rendering every context consumer per frame.
- 5px click-vs-drag threshold so taps don't jitter the window
- One-frame `transition: none` skip on drag commit so the window
snaps to the new position without a ghost animation
- Zoom-from-origin open animation: first render at the clicked icon's
CRT-relative rect, then flipped to the cascaded position on the
next frame so CSS interpolates the delta
- Lazy content mount — app component only renders after the entry
animation completes (skipped under prefers-reduced-motion)
- Drag is constrained to the CRT screen bounds via container ref
DesktopIcon (app/components/desktop/desktop-icon.tsx)
- Single click selects (invert colors), double click opens and
captures the icon's rect for the zoom-from-origin animation
- `large` prop scales the icon/label up when the desktop is maximized
Desktop shell (app/components/desktop/desktop.tsx)
- Wraps children in WindowManagerProvider
- 2-column icon grid in the top-right so all 8 apps fit in the small
CRT; icons scale up in maximize mode
- Decorative Trash bottom-right with pointer-events: none
- Windows layer renders every open window; click on bare desktop
deselects the active icon
MaximizeNudge (app/components/desktop/maximize-nudge.tsx)
- Small Mac-style tip dialog that appears once after the first app
opens, suggesting Full Screen. Auto-dismisses when user maximizes
or clicks Not Now. Persisted to sessionStorage.
Menu bar refactor (app/components/menu-bar.tsx)
- Now reads derived activeWindowId from WindowManagerContext
- Apple-glyph menu always leftmost (SVG apple)
- Falls back to Finder defaults when no window is focused
- Per-app menuItems (when provided) override the defaults
- z-index 9999 so dropdowns cover windows
- No longer mounted in welcome-screen.tsx
page.tsx
- Desktop phase now renders <Desktop /> (the real shell) instead of
the Phase 1 placeholder text
- Under-construction indicator removed from the welcome screen
Assets
- public/app-icons/browser.svg added to round out all 8 app icons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trash was previously a decorative element pinned bottom-right with pointer-events: none. It's now a real entry in APP_REGISTRY with a Coming Soon placeholder component and a Finder-style status bar. Desktop shell renders Trash as a normal DesktopIcon at bottom-right (filtered out of the top-right 2-column grid so layout is preserved), so single-click selects and double-click opens with the usual zoom-from-origin animation. DESKTOP-PLAN.md Decision #11 updated to match — Trash is clickable but not functional beyond opening. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scaffolds the shadcn/ui context-menu component (app/components/ui/ context-menu.tsx) and wires it into desktop icons and the bare desktop surface. shadcn component - Installed via `shadcn add context-menu` which added @radix-ui/react-context-menu to package.json - File rewritten to drop the lucide-react dependency and the Tailwind v3 theme tokens (bg-popover, rounded-md, animate-in, …) that don't exist in our Tailwind v4 alpha setup - Same export surface as stock shadcn, restyled inline with Mac OS 1 aesthetics: Chicago font, white bg, 1px black border, 2px black drop shadow, text glyphs (▶, ✓, ●) in place of lucide icons - Hover/keyboard-focus highlighting driven by Radix's data-highlighted attribute via a small rule in global.css Right-click handlers - DesktopIcon: App name label → Open, Get Info, Duplicate, Move to Trash. Right-click also selects the icon so it shows the highlight state before the menu opens. Icon is split into DesktopIcon (handles context menu + data) and IconButton (forwardRef button used by ContextMenuTrigger asChild). - Desktop surface: Curly OS label → Go Full Screen / Restore Screen (live-toggles based on isMaximized), Clean Up Desktop, Change Wallpaper, About This Macintosh. Wrapped the desktop container in ContextMenu/ContextMenuTrigger so empty-area right-clicks work. Icon highlight fixes - whiteSpace: normal on the label so two-word names (Curly Browser, Control Panel, Note Pad, World Map) wrap at the space and the black selection background covers the whole label instead of overflowing the button width - outline: none on the icon button so the browser's default blue focus ring doesn't appear after a right-click - Removed the filter: invert(1) on icon images when selected — the label highlight alone is enough; the icon art stays untouched Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Boot icon - SIZING.macIconSize reduced ~30% (100 → 70 desktop, 60 → 42 mobile) so the Happy Mac on the boot screen sits more comfortably in the CRT frame. Chin toggle - The chin used to be stacked (CD slot on top, separate circular button below) and the button was only rendered on welcome/desktop phases. This caused a visible chin-height shift when the screen transitioned out of boot. Now there is no separate button — the CD slot itself IS the maximize toggle. Always rendered (disabled during boot) so the chin height is constant across phases. - Hover brightens the slot with a soft white glow to hint at interactivity without breaking the iMac hardware aesthetic. - aria-label flips between "Maximize screen" and "Restore screen". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Foundation: Mac OS 1984 desktop — maximize, windows, shell, menus, context menus
Lock in Browser (bookmarks launcher, no iframes), Finder (public/ mirror with Mac-styled folders), World Map (react-simple-maps + 24 visited countries), and Music (Spotify live now-playing with vinyl presentation). Update readiness checklist — all 8 apps unblocked; Music gated only on Spotify OAuth setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-installed on base so all app branches inherit the deps without each needing its own install commit. react-simple-maps wraps d3-geo and handles TopoJSON rendering; world-atlas provides the country TopoJSON data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
getFilename was used once; inlined as file.path.split('/').pop(). The
middle breadcrumb branch was unreachable — pathStack only ever holds
valid folder ids (handleOpen only pushes on kind==='folder').
Removes the 3 inline <style> tags and 3 fragment wrappers from music.tsx. The reduced-motion override is already handled globally (animation-duration: 0.01ms) and by shouldSpin gating, so the local @media rule was redundant. className="curly-vinyl" also dropped — animation is driven entirely by inline style.
Trivial one-liners (handleOpen, openInNewTab, copyLink) inlined. The inline 'opacity: 0.4, cursor: default' on disabled menu items was inconsistent with every other disabled ContextMenuItem in the app (finder/desktop/desktop-icon all use bare 'disabled'); global CSS already handles the look via [data-disabled] + pointer-events: none.
…ech-icons textareaRef was assigned but never read; the onFocus handler duplicated outline: none already in the textarea's style. public/tech-icons/ was empty; removed from filesystem and from the finder folder-rename list in DESKTOP-PLAN (never implemented).
makePlaceholder was used once (for Trash); inlined as component: () => <ComingSoon name="Trash" />. The bearer-auth + no-store options pattern was duplicated 3 times in the spotify route; extracted as bearerOpts(token) so multi-line fetch calls collapse to one line.
Phase 5 polish items were all marked done per commit 3939e93. The file was never referenced from code or other docs and duplicated commit history.
Both the <pattern> tag's 4 attrs (world-map) and the outer flex <div> (not-found) fit comfortably on one line.
Sweep across 10 files — chicago, nav/chrome/tile base styles, content/item styles in the context-menu wrapper, calculator btnBase, etc. All were 4–6 lines with no CSSProperties value that needed per-line separation.
Implementation step-by-step tables (Phases 1–5) and Subagent Contracts template were execution artifacts for work now complete. Design Decisions, Architecture, Applications specs, and Inspiration/Anti- patterns retained. Updated ToC to match.
The PR #3 workflow steps and "Why This Strategy" rationale were execution notes for completed work. Branch tree retained as the canonical merge path.
Spotify route returns genres already sorted descending by count; the client's defensive re-sort added no behavior. Pass data.genres directly.
…ne line Across curly-browser/music/scrapbook/crt-screen/maximize-nudge/imac-frame/layout. The verbose 'destructure + type' block form gained no clarity vs the one-line signature.
…ach" This reverts commit 73e891d.
…licate import" This reverts commit ab6b538.
…res to one line" This reverts commit 6ff5cf9.
…nuDropdown props" This reverts commit 65417f8.
…line" This reverts commit 5d8d2c4.
…s to one line" This reverts commit a1bc0ae.
This reverts commit 9257aa5.
…iners" This reverts commit fb56a3e.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
raj-portfolio | 97931b8 | Commit Preview URL Branch Preview URL |
Apr 17 2026, 07:12 PM |
7 tasks
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.
Summary
window,window-manager, menu/context helpers, and app registry wiring) without changing product behavior.Test plan
pnpm lintpnpm typecheckMade with Cursor