billions must vibecode! - opencode.md creation
This commit is contained in:
parent
cd1766619d
commit
709fba188c
1 changed files with 192 additions and 0 deletions
192
opencode.md
Normal file
192
opencode.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# opencode.md — cold-boot notes
|
||||
|
||||
Quick context so a fresh session (no memory of this project) can be productive in
|
||||
a minute. Read this first, then skim the files it points at.
|
||||
|
||||
---
|
||||
|
||||
## What this project is
|
||||
|
||||
Personal portfolio site for **dvorakmortonxd.dev**. It's not a normal website —
|
||||
it ships **two fully interactive desktop/OS simulations** plus a terminal, and
|
||||
picks which one to show based on theme and device.
|
||||
|
||||
- Framework: **Next.js 16** (App Router, Turbopack), **React 19**, TypeScript.
|
||||
- Styling: plain CSS files (`page.css`, `skeuo.css`), no CSS-in-JS, no Tailwind
|
||||
utilities in the components (Tailwind is present via PostCSS but the look is
|
||||
hand-written CSS).
|
||||
- Path alias: `@/*` → `src/*`.
|
||||
- Version: `2.1.0` (see `package.json`).
|
||||
|
||||
### Scripts
|
||||
```bash
|
||||
npm run dev # next dev --turbopack (a server may already be running on :3000)
|
||||
npm run build # next build --turbopack
|
||||
npm run start # next start
|
||||
npm run lint # eslint
|
||||
```
|
||||
Useful checks before handing work back: `npx tsc --noEmit`, `npx eslint <files>`,
|
||||
`npm run build`.
|
||||
|
||||
### Git
|
||||
Branch is **main**. Remote is a self-hosted Gitea at
|
||||
`git.dvorakmortonxd.dev`. **The remote URL contains a plaintext access token.**
|
||||
Do not print it, copy it into new files, or push it anywhere else.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/app/
|
||||
page.tsx → renders <ThemeRoot/>, imports page.css + skeuo.css
|
||||
layout.tsx → html shell, ThemeScript
|
||||
data.ts → friends[], WALLPAPER, MOBILE_WALLPAPER (static content)
|
||||
page.css → base / terminal theme styles
|
||||
skeuo.css → Windows/Plasma desktop + mobile home styles
|
||||
components/
|
||||
ThemeRoot.tsx → picks which shell to show (the router of the UI)
|
||||
TerminalLayout.tsx → "terminal" theme (Gruvbox)
|
||||
WindowsDesktop.tsx → "skeuo" theme, desktop: icons, windows, taskbar
|
||||
MobileHome.tsx → "skeuo" theme, phone: app grid + dock
|
||||
WinWindow.tsx → a single draggable/resizable window
|
||||
apps.tsx → APPS registry + the content of each app
|
||||
useDesktopIcons.ts → desktop icon grid + drag/drop (THE KEY FILE, see below)
|
||||
useDragOrder.ts → simple list reordering (mobile home screen only)
|
||||
useTheme.ts → theme state via useSyncExternalStore
|
||||
ThemeScript.tsx → inline pre-hydration script: theme + device detection
|
||||
```
|
||||
|
||||
### How UI selection works
|
||||
1. `ThemeScript` runs before paint. It reads `localStorage.theme` and sets
|
||||
`data-theme` (`skeuo` | `terminal`) and `data-device` (`phone` | `desktop`)
|
||||
on `<html>` from a user-agent sniff.
|
||||
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via
|
||||
`useSyncExternalStore` and dispatches a `theme-change` event on toggle.
|
||||
3. `ThemeRoot` shows the terminal when `theme === "terminal"`; otherwise it
|
||||
mounts **both** `WindowsDesktop` and `MobileHome` and lets CSS
|
||||
(`html[data-device="phone"]`) decide which is visible. The desktop stays
|
||||
mounted while hidden so the Welcome window isn't recreated.
|
||||
|
||||
Themes: `skeuo` = KDE Plasma / Windows desktop, `terminal` = Gruvbox terminal.
|
||||
|
||||
---
|
||||
|
||||
## `useDesktopIcons.ts` — the desktop icon grid (read this carefully)
|
||||
|
||||
This is the most edited and most subtle file in the repo. It owns desktop icon
|
||||
placement: both the default layout and drag-and-drop. If something is wrong
|
||||
with icons, it's almost certainly here.
|
||||
|
||||
### Design
|
||||
- The three icon groups (apps on the left, services top-right, friends
|
||||
bottom-right) are **real flex stacks** in the DOM at `.win-icons` (plus
|
||||
`.win-icons-tr` / `.win-icons-br`). They are rendered **hidden** with class
|
||||
`win-measure` purely so the grid can be measured from them.
|
||||
- On mount (a `useLayoutEffect`), `measure()` reads the real rendered boxes of
|
||||
those stacks: cell size = icon width/height **+ the CSS flex gap** (`GRID_GAP`,
|
||||
currently 4px), and origin = the left stack's offset. From that it computes
|
||||
`cols` / `rows` for the whole desktop.
|
||||
- `defaultCells()` assigns each icon the cell its stack already occupies. So
|
||||
**the grid you see on load is the grid dragging uses** — no re-layout jump.
|
||||
- Icons are then rendered absolutely positioned in `.win-icon-layer`, driven by
|
||||
the `positions` map (`{ [iconId]: {x, y} }`).
|
||||
- Dragging: `onPointerDown` builds a **ghost** (a clone of the button appended
|
||||
to `document.body`, moved with `translate()` under a `grabbing` cursor). The
|
||||
ghost is built imperatively on purpose — moving it must not re-render React.
|
||||
- Drops snap to a cell. `normalize()` is the single guard that enforces
|
||||
**one icon per cell**: it assigns icons in a stable order and bumps any
|
||||
collision to the nearest free cell. Every mutation path (initial layout,
|
||||
drop, window resize) goes through it.
|
||||
|
||||
### Invariants — do not break these
|
||||
1. **One icon per cell.** All position mutations must run through `normalize()`.
|
||||
2. **The grid must not change shape on first drag.** Default cells come from
|
||||
measuring the real layout; don't hardcode cell sizes.
|
||||
3. **The init effect must run exactly once.** It depends only on `iconKeys`,
|
||||
which is the module-level constant `ICON_KEYS` in `WindowsDesktop.tsx`. If
|
||||
you pass a fresh array literal here you WILL get "Maximum update depth
|
||||
exceeded" (this bug has already happened once — see history below).
|
||||
|
||||
### Gotcha: native drag hijacks the pointer gesture
|
||||
Icons whose glyph is an `<img>` (the friend shortcuts, which use `next/image`
|
||||
with `/skopec.svg`, `/bukovina.svg`, `/vlcak.svg`) can start the browser's
|
||||
**native image drag**, which fires `pointercancel` and kills the custom drag.
|
||||
Emoji-based icons are unaffected, which makes this look like "only some icons
|
||||
don't drag".
|
||||
|
||||
Guard (already in place, keep it that way):
|
||||
- `draggable={false}` on the `<Image>` in `renderIcon`.
|
||||
- CSS `-webkit-user-drag: none` on `.win-icon` and its children.
|
||||
- CSS `pointer-events: none` on `.win-icon-glyph img` / `.win-icon-label` so
|
||||
`e.target` is always the button.
|
||||
|
||||
### Related
|
||||
`useDragOrder.ts` is a **different, simpler** hook used only by `MobileHome` for
|
||||
reordering the phone app grid/dock (drag one item over another to swap). It is
|
||||
list reordering, not free placement. Don't confuse the two.
|
||||
|
||||
---
|
||||
|
||||
## History of the desktop-icon work (why the code looks like this)
|
||||
|
||||
1. **Free placement.** First version let icons go anywhere. Worked, but icons
|
||||
could overlap and the layout jumped into a grid on first drag.
|
||||
2. **Grid snapping.** Dropped icons snap to a grid cell; dropping onto an
|
||||
occupied cell bumps the occupant.
|
||||
3. **Grid bug fixes.** The cell pitch originally omitted the 4px flex gap, and
|
||||
the drop calculation rounded differently from the occupancy check, so two
|
||||
icons could land in the same cell. Fixed by deriving pitch from measured
|
||||
boxes **plus gap**, and by routing everything through `normalize()`.
|
||||
4. **Layout no longer jumps.** Grid measured from the real stacks and applied
|
||||
before paint, so load layout == drag layout.
|
||||
5. **Infinite loop fix.** `iconKeys` was an inline array literal in the effect
|
||||
deps → the effect re-ran every render → `setState` → loop. Hoisted to the
|
||||
module constant `ICON_KEYS` and dropped the unstable callbacks from deps.
|
||||
6. **Friend icons couldn't be dragged.** Native image drag on the
|
||||
`next/image` glyph fired `pointercancel`. Fixed with `draggable={false}` +
|
||||
`-webkit-user-drag: none`.
|
||||
|
||||
---
|
||||
|
||||
## Conventions & things that will bite you
|
||||
|
||||
- Components are client components (`"use client"` at the top) — they use
|
||||
pointer events and window APIs freely.
|
||||
- CSS classes are prefixed by area: `win-` (desktop), `mobile-` (phone),
|
||||
`app-` / `kickoff` / `terminal` etc. Grep by prefix to find styles.
|
||||
- Several places deliberately use **imperative DOM work** (drag ghost,
|
||||
transform-origin for the mobile launch animation). This is intentional for
|
||||
performance; prefer matching the existing pattern over "Reactifying" it.
|
||||
- `setState` inside an effect is blocked by the ESLint config
|
||||
(`react-hooks/set-state-in-effect`). The one intentional exception is the
|
||||
grid init in `useDesktopIcons.ts`, which carries a scoped
|
||||
`eslint-disable-next-line` with a comment. Don't add more without a reason.
|
||||
- Watch for `pointercancel`: any custom pointer gesture over an `<img>`, link,
|
||||
or text selection needs `draggable=false` / `user-select: none`.
|
||||
- Reduced motion is handled with a near-zero `animation-duration` (not
|
||||
`animation: none`) so `animationend` still fires and windows unmount.
|
||||
|
||||
## Testing notes
|
||||
- There is **no test suite**. Verification is `tsc --noEmit`, `eslint`, and
|
||||
`next build`.
|
||||
- A dev server may already be running on port 3000; check before starting one.
|
||||
- The desktop layout is geometry-heavy. When changing grid maths, a small
|
||||
standalone script mirroring `normalize()` / `posToCell()` is a fast way to
|
||||
prove one-icon-per-cell holds for edge cases (offscreen drops, tiny
|
||||
viewports, repeated drops on one cell).
|
||||
|
||||
---
|
||||
|
||||
## After finishing — push to git
|
||||
|
||||
When the work is done, run **only** these three commands (no extra flags, no
|
||||
rebasing, no force-push):
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "<WHAT CHANGED>"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Replace `<WHAT CHANGED>` with a short, specific summary of the change.
|
||||
Loading…
Add table
Add a link
Reference in a new issue