This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
bun install # Install dependencies (uses bun.lock)
bun dev # Start dev server at 0.0.0.0:3000
bun run build # Build for production (Nuxt/Nitro) + run scripts/fix-unhead-bundle.mjs
bun run generate # Static site generation
bun run preview # Preview production build
bun run scripts/build-pack.ts --tileset <id_string> # Build a distributable asset pack → ./packs/<slug>/scripts/build-pack.ts turns a published tileset into a distributable folder (packed PNG, Godot
.tres, Tiled .tsx, JSON, palette, cover, README/LICENSE/CREDITS). It renders the sheet with
sharp but lays it out via app/helper/sheet-layout.ts — the same helper the editor uses — so the
offline PNG is identical to the in-browser download. Tilesets are private by default in the API;
pass --token <jwt> for anything not status=public.
No test or lint commands are configured. The build script chains nuxt build && node ./scripts/fix-unhead-bundle.mjs — the post-step copies missing unhead/dist/* files into .output/server/node_modules/ to work around a Nitro bundling gap that crashes prod runtime with ERR_MODULE_NOT_FOUND on unhead/server.
SimplePixelArt.com — a pixel art creation and discovery platform built with Nuxt 4 (Vue 3, SSR hybrid).
- Nuxt 4 — source lives under
app/(Nuxt 4 convention), notsrc/ - Pinia for state management (
app/stores/) - Pure CSS design system in
app/assets/css/main.css(no Tailwind at runtime; a minimal preflight replaces it) - Nitro server routes for sitemaps (
server/routes/sitemap-*.xml.ts) - External API:
https://touch.ninosaur.com— all data (art, users, tags, collections) comes from here. Configurable viaNUXT_PUBLIC_API. The backend is a separate hosted service and is not part of this repository. - Auth: Cookie-based Bearer tokens (
auth_tokencookie viauseStatefulCookie), initialized inapp/plugins/auth.client.tsandauth.server.ts - Local Nuxt module:
modules/custom-icons-standalone/— auto-registered via Nuxt'smodules/directory convention. It scans.vuefiles foricon-<name>classes and generatesapp/assets/css/icons.css(gitignored, rebuilt on every dev/build) withmask-imagerules pointing atpublic/icons/<name>.svg. To add an icon: drop the SVG intopublic/icons/and useclass="icon icon-<name>".
app/pages/— file-based routes. Detail pages use the[id_string].vuedynamic param convention (e.g.art/[id_string].vue,arts/[id_string].vue,creator/[id_string].vue,work/[collection].vue)app/components/— auto-imported;ui/for generic UI (Tooltip, DropdownMenu, Modal, etc.),editor/for editor-specific (Palette, Timeline, TilesetStrip),partial/for Header/Footer,item/for Card/Listapp/stores/—editor.store.ts(canvas/editor state, multi-board workspace, undo/redo, auto-save),auth.store.ts(session/token)app/composables/—useCustomFetch.tsexportsuseNativeFetch(promise-based$fetch) anduseAuthFetch(reactiveuseFetch); both inject the Bearer token and base URL. AlsouseCustomSeoMeta,useLocalTilesets,useStatefulCookie.app/helper/— pure utility modules:canvas.ts(image processing/pixel rendering),color.ts,utils.ts,anim-export.ts(GIF/spritesheet),sheet-layout.ts,workspaceSnapshot.ts(IndexedDB multi-board snapshot),constants.tsapp/types/index.ts— shared TypeScript interfaces (EditorData,SharedPage,Layer,User,APIResponse<T>, etc.)
The main pixel art editor is the most complex part of the app:
editorDataholds the full canvas state (layers, palette, size);boards[]+activeBoardIddrive the multi-board infinite-canvas workspacevirtualLayeris a temporary rendering layer for real-time drawing previewhistoryarray with index enables undo/redo- Mirror mode (horizontal/vertical), animation frames, and iso (dimetric) grid mode are supported
- Debounced auto-save: guests persist to localStorage/IndexedDB; signed-in users save to the cloud. Guest work migrates to the cloud on sign-in.
The heaviest helper module. It handles layer-to-pixel-map conversion, image-to-grid analysis with threshold-based sampling, color processing, crop detection, and iso lattice path building.
All API calls go through useNativeFetch / useAuthFetch (from app/composables/useCustomFetch.ts), which inject the auth Bearer token from the auth_token cookie and route to runtimeConfig.public.api. Response shape is APIResponse<T> with pagination. Use useAuthFetch in SSR-aware page-level calls; useNativeFetch for imperative calls (e.g. inside store actions).
useCustomSeoMetacomposable handles OG/Twitter meta injection per page — pass refs/getters for query-dependent values (robots/canonical) so client-side query navigation stays correct- JSON-LD structured data is added per page and globally in
nuxt.config.ts - Dynamic XML sitemaps are generated via Nitro server routes in
server/routes/ strictNullChecksis disabled in tsconfig
- Icons: MDI-style SVGs in
public/icons/, used viaclass="icon icon-<name>"(masks, colored bycurrentColor) - Modals: use the shared
UiModalcomponent (share-overlay chrome) — don't hand-roll overlays - One flat-panel frame for canvas tools (
.editor/.flat-editorchrome in main.css); widgets sit flush with 1px dividers - Match the package manager to the lockfile:
bun.lock→ bun
This is a public open-source repo — commits and releases follow a strict convention (also documented for contributors in CONTRIBUTING.md).
main is always deployable — every push to main auto-deploys production.
Work on a branch, merge when done:
feat/<short-slug> new feature feat/onion-skin-opacity
fix/<short-slug> bug fix fix/fullscreen-pointer-events
perf/<short-slug> performance perf/iso-lattice-lines
ui/<short-slug> visual/UX polish ui/toolbar-tooltips
refactor/<short-slug> no behavior change refactor/palette-store
docs/<short-slug> docs only docs/readme-screenshot
chore/<short-slug> build/config/deps chore/deploy-secrets
Trivial single-commit changes (typo, doc line) may go straight to main; anything
touching editor behavior gets a branch.
Format: <emoji> <type>(<scope>): <subject>
| Emoji | Type | Use for |
|---|---|---|
| ✨ | feat |
new user-facing feature |
| 🐛 | fix |
bug fix |
| ⚡ | perf |
performance improvement |
| 💄 | ui |
visual/UX polish, CSS |
| ♻️ | refactor |
code change, same behavior |
| 📝 | docs |
README/docs/comments only |
| 🔧 | chore |
build, config, deps, CI |
| 🔒 | security |
security fix |
| 🚑 | hotfix |
urgent prod fix on main |
| 🧹 | cleanup |
dead code removal |
- Scope = area touched:
editor,tileset,tilemap,palette,animation,convert,work,gallery,seo,deploy(omit if truly global) - Subject: imperative, lowercase, no trailing period, ≤ 72 chars
- Body (when non-obvious): the why and the user-visible effect; wrap at 72
- One logical change per commit — don't mix a feature with unrelated cleanup
Examples:
✨ feat(editor): single-key tool shortcuts (B/E/G/V/M/L)
🐛 fix(editor): whole-canvas flip was off by one pixel
⚡ perf(editor): draw iso lattice as line families, not per-diamond quads
💄 ui(work): pin panel to viewport height with internal scroll
📝 docs: add editor screenshot to README
- SemVer annotated tags:
git tag -a vX.Y.Z -m "vX.Y.Z"onmainafter the deploy for that commit is green - patch = fixes/polish · minor = new features · major = breaking/redesign
- Every tag gets a GitHub Release with notes structured as:
## ✨ Highlights→## 🐛 Fixes→## 🧰 Internal(skip empty sections; one bullet per change, user-facing wording, link PRs/issues when they exist) - Tag pushes do NOT trigger deploy (workflow only watches branch
main)