Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fc10bc6
Update Next.js breaking changes documentation visibility
unclesp1d3r Jun 28, 2026
4e4208c
feat(platform): U1 foundation — Postgres, Drizzle, Docker, bun test h…
unclesp1d3r Jun 28, 2026
0855c8e
feat(auth): U2 Better Auth — email+password, admin accounts, rate lim…
unclesp1d3r Jun 28, 2026
9dbc256
feat(db): U3 core schema — firearms, magazines, join+ordinal, grants,…
unclesp1d3r Jun 28, 2026
e245ce0
feat(auth): U4 scoping & authorization layer — visible-set, grants, w…
unclesp1d3r Jun 28, 2026
f078893
feat(domain): U5 firearms — all-failures validation + visibility-scop…
unclesp1d3r Jun 28, 2026
e23c005
feat(domain): U6 magazines + compatibility — ordinal, dedup, FK-visib…
unclesp1d3r Jun 28, 2026
8921d1a
feat(domain): U7 summary — viewer-relative aggregation (parity §7)
unclesp1d3r Jun 28, 2026
5cd7e91
feat(domain): U8 CSV export — RFC-4180 + injection guard + viewer-rel…
unclesp1d3r Jun 28, 2026
86429b5
feat(domain): U9 search & filter — three AND filters with LIKE escaping
unclesp1d3r Jun 28, 2026
44b3ec0
feat(infra): U12 write-safety — idempotency store, mutation rate limi…
unclesp1d3r Jun 28, 2026
4604990
feat(domain): U10 bulk add — label algorithm, sequence continuation, …
unclesp1d3r Jun 28, 2026
a93b3a3
feat(ui): U13 app shell + auth UI — login, gated layout, nav, admin a…
unclesp1d3r Jun 28, 2026
e9e32c7
feat(domain): U11 reference data — curated lists, distinct-caliber union
unclesp1d3r Jun 28, 2026
eb8d593
feat(ui): U14 inventory UI — firearms & magazines forms/lists + carri…
unclesp1d3r Jun 28, 2026
6db31a4
feat(ui): U15 insight UI — summary view, CSV export, search/filter co…
unclesp1d3r Jun 28, 2026
86efdab
feat(ui): U16 sharing UI — grant/revoke item access + create-on-behal…
unclesp1d3r Jun 28, 2026
8b1a864
fix(docker): build with placeholder env; ship auth.ts + scripts for s…
unclesp1d3r Jun 28, 2026
b4cf812
docs: deployment guide (TLS note) + remove unused scaffold assets
unclesp1d3r Jun 28, 2026
1e38ef0
docs: add the homelab web replatform implementation plan for traceabi…
unclesp1d3r Jun 28, 2026
7482c56
chore(config): add claude plugin configuration
unclesp1d3r Jun 29, 2026
9df53aa
feat: add skills lock file
unclesp1d3r Jun 29, 2026
d103f5a
docs: project README — user-first (ranges, clubs, individuals)
unclesp1d3r Jun 29, 2026
1417405
docs: project README — user-first (ranges, clubs, individuals)
unclesp1d3r Jun 29, 2026
15afe61
fix(docker): keep public/ tracked so the image build doesn't break
unclesp1d3r Jun 29, 2026
6b5a3f1
feat(ui): The Machined Console — two-mode theme (dark default, system…
unclesp1d3r Jun 29, 2026
94f26cb
docs(design): Impeccable context — PRODUCT.md + DESIGN.md (The Machin…
unclesp1d3r Jun 29, 2026
bcf7dbe
style(ui): bolder — sharper inventory hierarchy (product clarity, not…
unclesp1d3r Jun 29, 2026
07a3e62
feat(ui): completion feedback, accessible delete, and cold-start onbo…
unclesp1d3r Jun 29, 2026
40225c3
docs(README): clarify bulk-add syntax
unclesp1d3r Jun 29, 2026
0a31683
fix(db,bulkadd): build without DATABASE_URL; bound bulk-add label all…
unclesp1d3r Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"enabledPlugins": {
"impeccable@impeccable": true,
"typescript-lsp@claude-plugins-official": true,
"playwright@claude-plugins-official": true,
"chrome-devtools-mcp@claude-plugins-official": true
}
}
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Keep secrets and local cruft out of the build context.
.env
.env.*
!.env.example

node_modules
.next
out
build
coverage

.git
.github
.vscode
.claude
.agents
docs

*.tsbuildinfo
npm-debug.log*
.DS_Store
26 changes: 26 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copy to `.env` (gitignored) and fill in. Never commit real secrets.

# --- Database ---------------------------------------------------------------
POSTGRES_USER=magstacker
POSTGRES_PASSWORD=change-me-in-production
POSTGRES_DB=magstacker
# Host port the db is published on (kept off 5432 to avoid clashing with a host
# Postgres). The app container reaches the db by service name on 5432.
POSTGRES_HOST_PORT=5544

# DATABASE_URL is NOT set here: docker compose builds it per-service inline
# (host `db`). For local tooling (`bun test`, `bun run db:migrate`) export it
# yourself pointing at the published host port, e.g.:
# export DATABASE_URL=postgres://magstacker:change-me-in-production@localhost:5544/magstacker

# --- Auth (Better Auth, added in U2) ----------------------------------------
# Generate a strong random secret, e.g. `openssl rand -base64 32`.
BETTER_AUTH_SECRET=change-me-generate-a-strong-random-secret
BETTER_AUTH_URL=http://localhost:3000

# First-admin bootstrap for `bun run seed:admin` (one-time, fresh deployment).
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-me-strong-admin-password

# --- App --------------------------------------------------------------------
APP_HOST_PORT=3000
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example

# vercel
.vercel
Expand All @@ -42,3 +43,9 @@ next-env.d.ts

# local env files
**/*.local.*


# AI assistant
.agents/skills
.claude/skills/*
!.claude/skills/skill-library
198 changes: 198 additions & 0 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"schemaVersion": 2,
"title": "Design System: The Machined Console",
"extensions": {
"colorMeta": {
"anodized": {
"role": "primary",
"displayName": "Anodized Orange",
"canonical": "#ffb240 (dark) / #bd4620 (light)",
"tonalRamp": [
"#2a1a05",
"#5c3a0c",
"#8a5612",
"#b8741c",
"#e09433",
"#ffb240",
"#ffc874",
"#ffe0ad"
]
},
"paper": {
"role": "neutral",
"displayName": "Console Graphite / Matte Paper",
"canonical": "#15181c (dark) / #f3f2ee (light)",
"tonalRamp": [
"#0d0f12",
"#15181c",
"#1a1e24",
"#252b33",
"#39414a",
"#5a6571",
"#8a929c",
"#c5ccd4"
]
},
"ink": {
"role": "neutral",
"displayName": "Ink",
"canonical": "#e9edf1 (dark) / #1e1c19 (light)",
"tonalRamp": [
"#11151a",
"#2a3037",
"#4a525c",
"#6b7682",
"#8a929c",
"#c5ccd4",
"#e9edf1",
"#f6f8fa"
]
}
},
"typographyMeta": {
"display": {
"displayName": "Display",
"purpose": "Page titles only; fixed rem, never fluid."
},
"label": {
"displayName": "Mono Label",
"purpose": "Stamped uppercase kicker / column header; the machined tell."
},
"data": {
"displayName": "Data",
"purpose": "Every number/serial; mono + tabular figures."
}
},
"shadows": [
{
"name": "raised",
"value": "0 1px 2px rgb(40 36 30 / 0.06), 0 2px 8px rgb(40 36 30 / 0.05)",
"purpose": "Whisper under cards/tables (light). Near-invisible on dark graphite."
},
{
"name": "glow-blaze",
"value": "0 0 16px rgb(255 178 64 / 0.30)",
"purpose": "Dark-mode state signal: the primary/lit control is energized."
},
{
"name": "inset-blaze",
"value": "0 1px 0 #9c3a18, 0 1px 3px rgb(0 0 0 / 0.18)",
"purpose": "Light-mode machined inset on the primary control (same token as glow)."
}
],
"motion": [
{
"name": "ease-out-expo",
"value": "cubic-bezier(0.16, 1, 0.3, 1)",
"purpose": "Default easing; confident exponential ease-out, no bounce."
},
{
"name": "icon-swap",
"value": "180ms ease-out, rotate -90/0/90 + opacity",
"purpose": "Theme-toggle icon crossfade (Motion). Reduced-motion: instant."
}
],
"breakpoints": [
{ "name": "sm", "value": "640px" },
{ "name": "lg", "value": "1024px" }
]
},
"components": [
{
"name": "Primary Button",
"kind": "button",
"refersTo": "button-primary",
"description": "The one anodized action; glows in dark, inset in light.",
"html": "<button class=\"ds-btn-primary\">Add magazine</button>",
"css": ".ds-btn-primary { display:inline-flex; align-items:center; height:40px; padding:0 16px; font:500 13px/1 system-ui,sans-serif; color:#1a1205; background:#ffb240; border:1px solid transparent; border-radius:6px; box-shadow:0 0 16px rgb(255 178 64 / .30); cursor:pointer; transition:filter .15s; } .ds-btn-primary:hover { filter:brightness(1.05); } .ds-btn-primary:active { filter:brightness(.95); } .ds-btn-primary:focus-visible { outline:2px solid #ffb240; outline-offset:2px; }"
},
{
"name": "Ghost Button",
"kind": "button",
"refersTo": "button-ghost",
"description": "Low-emphasis row action (Edit / Share / Sign out).",
"html": "<button class=\"ds-btn-ghost\">Edit</button>",
"css": ".ds-btn-ghost { display:inline-flex; align-items:center; height:32px; padding:0 12px; font:500 13px/1 system-ui,sans-serif; color:#c5ccd4; background:transparent; border:1px solid transparent; border-radius:6px; cursor:pointer; transition:background .15s,color .15s; } .ds-btn-ghost:hover { background:#11151a; color:#e9edf1; }"
},
{
"name": "Text Input",
"kind": "input",
"refersTo": "input",
"description": "Field shell; mono+tabular value, anodized focus.",
"html": "<input class=\"ds-input\" value=\"9mm\" />",
"css": ".ds-input { height:40px; padding:0 12px; width:220px; font:400 13px/1 ui-monospace,monospace; font-variant-numeric:tabular-nums; color:#e9edf1; background:#1a1e24; border:1px solid #39414a; border-radius:6px; } .ds-input:hover { border-color:#8a929c; } .ds-input:focus-visible { outline:none; border-color:#ffb240; box-shadow:0 0 0 1px #ffb240; }"
},
{
"name": "Compatibility Badge",
"kind": "chip",
"refersTo": "badge",
"description": "Compatible-firearm / permission chip.",
"html": "<span class=\"ds-badge\">Glock 19</span>",
"css": ".ds-badge { display:inline-flex; align-items:center; padding:4px 8px; font:600 11px/1 ui-monospace,monospace; letter-spacing:.04em; color:#ffb240; background:#2a2415; border:1px solid rgb(255 178 64 / .32); border-radius:999px; }"
},
{
"name": "Stat",
"kind": "card",
"refersTo": "table-header",
"description": "Tonal panel with the signature anodized tick-mark + mono label + tabular value.",
"html": "<div class=\"ds-stat\"><div class=\"ds-stat-k\">Total mags</div><div class=\"ds-stat-v\">24</div></div>",
"css": ".ds-stat { position:relative; overflow:hidden; padding:20px; min-width:160px; background:#1a1e24; border:1px solid #2a3037; border-radius:10px; } .ds-stat::before { content:''; position:absolute; left:20px; top:0; width:14px; height:2px; background:#ffb240; } .ds-stat-k { font:600 10.4px/1 ui-monospace,monospace; letter-spacing:.14em; text-transform:uppercase; color:#8a929c; } .ds-stat-v { margin-top:6px; font:600 30px/1 system-ui,sans-serif; letter-spacing:-.02em; font-variant-numeric:tabular-nums; color:#f3f6f9; }"
},
{
"name": "Table Row (lit)",
"kind": "custom",
"refersTo": "table-header",
"description": "Inventory row; the active/current row is 'lit' with a fill + accent dot, never a side stripe.",
"html": "<table class=\"ds-tbl\"><tr class=\"lit\"><td><span class=\"dot\"></span>Magpul PMAG</td><td>9mm</td><td class=\"num\">17</td></tr><tr><td style=\"padding-left:29px\">OEM USGI</td><td>5.56</td><td class=\"num\">30</td></tr></table>",
"css": ".ds-tbl { border-collapse:collapse; width:100%; font:400 13.5px/1 system-ui,sans-serif; color:#c5ccd4; background:#1a1e24; } .ds-tbl td { padding:11px 14px; border-bottom:1px solid #232a31; } .ds-tbl .num { font-family:ui-monospace,monospace; font-variant-numeric:tabular-nums; text-align:right; color:#f3f6f9; } .ds-tbl tr.lit td { background:#1f242b; color:#eef2f6; } .ds-tbl tr:hover td { background:#20262d; } .ds-tbl .dot { display:inline-block; width:7px; height:7px; margin-right:8px; border-radius:50%; background:#ffb240; box-shadow:0 0 8px #ffb240; }"
}
],
"narrative": {
"northStar": "The Machined Console",
"overview": "One instrument with two faces. Dark (default) = Field Console: graphite readout, high-contrast, the anodized accent runs bright and lights up active state. Light = Machined Instrument: matte near-white tool surface, deeper burnt-orange accent, primary controls carry a machined inset. Precision gear you trust; rejects both the generic-SaaS-dashboard and the sterile-corporate-tool, and never goes cute. Delight is earned through craft.",
"keyCharacteristics": [
"Two modes, one identity: dark Field Console (default) / light Machined Instrument",
"Anodized-orange accent for action/selection/lit state only, never decoration",
"Tabular monospace for every number, label, and serial",
"Tonal layering + 1px hairlines carry structure; shadow is incidental",
"Dense, scannable tables over airy marketing space"
],
"rules": [
{
"name": "The One Accent Rule",
"body": "Anodized orange is the only routine chromatic color and appears on ≤10% of a screen — the live control, current selection, lit row. Its rarity makes 'lit' read as signal.",
"section": "colors"
},
{
"name": "The No-Cream Rule",
"body": "The light surface is matte paper at near-zero warmth, not cream/sand/beige. Warmth lives in the accent.",
"section": "colors"
},
{
"name": "The Tabular Rule",
"body": "Every number is mono with tabular figures. Numbers that don't line up vertically are a defect.",
"section": "typography"
},
{
"name": "The Flat-Until-Lit Rule",
"body": "Surfaces are flat at rest, separated by tone and hairline. Only the accent visibly energizes, and only when it marks live state.",
"section": "elevation"
}
],
"dos": [
"Do keep anodized orange to ≤10% of a screen.",
"Do set every number, label kicker, and serial in mono with tabular figures.",
"Do build depth from tonal layers + 1px hairlines first; shadow last.",
"Do let the accent glow in dark only as a state signal; machined inset in light.",
"Do ship empty states that teach (the 'add your first…' CTAs)."
],
"donts": [
"Don't build a generic SaaS dashboard: no gradient cards, hero-metric template, or identical icon-card grids.",
"Don't ship a sterile corporate panel — restraint is not boring.",
"Don't go cute: no pastels, mascots, emoji, bounce, or elastic motion.",
"Don't use border-left/right > 1px as a colored accent stripe; mark active state with a lit fill + accent dot.",
"Don't use gradient text, decorative glassmorphism, or the accent as a background fill.",
"Don't put the light body on cream/sand/beige."
]
}
}
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

# Active ECC surface (trimmed)

This repo runs a **trimmed ECC surface**, not the full bundle. Full map + rationale:
Expand All @@ -13,3 +7,9 @@ This repo runs a **trimmed ECC surface**, not the full bundle. Full map + ration
- **DAILY (load by default):** frontend/react/next patterns, `docs-lookup`, `bun-runtime`, backend/postgres/migrations, `security-review`, TDD + e2e, code-review + on-stack reviewers (react/typescript/database/security), coding-standards, git-workflow, a11y, docker-patterns.
- **LIBRARY (search on demand, never auto-load):** all other languages (incl. **C#/.NET** — the former `.cs` snapshot is gone; behaviors distilled to `docs/reference/dotnet-extensions.md`), other web frameworks, domain verticals, network/homelab, content/marketing, research, heavy orchestration.
- **Hooks:** never wire ESLint/Prettier/pnpm hooks here — use `bun biome check`, `bun biome format`, `bun tsc --noEmit`.

<!-- BEGIN:nextjs-agent-rules -->
## This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
31 changes: 31 additions & 0 deletions AI_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# AI Usage Policy

We build operator-focused security tools. AI coding assistants are part of how we do that. This policy is not anti-AI -- it is pro-accountability.

Think of AI assistance like spellcheck. It catches typos, suggests corrections, and speeds up the mechanical parts of writing. But you are still responsible for your words and their consequences.

## The Rule

**You own every line you submit.** You must be able to explain what it does and how it interacts with the rest of the system without asking your AI to explain it back to you.

Everything else follows from that.

## How We Work

- **Disclose your tools.** Note what you used in your PR description -- Claude Code, Copilot, Cursor, whatever. No specific format required.

- **Review AI-generated text before posting.** Issues, discussions, and PR descriptions must reflect your understanding, not a language model's first draft. Read it, cut the filler, make sure it says what you mean.

- **No AI-generated media.** No generated images, logos, audio, or video. Text-based diagrams (ASCII art, Mermaid) and code are acceptable.

- **Unreviewed output gets closed.** Hallucinated APIs, boilerplate that ignores project conventions, suggestions you clearly did not run -- these get closed without review. We are not a QA service for your AI's output.

## Why

Transparent by design means knowing what the code does and why it is there. Tested under pressure means every change was understood by the person who submitted it. AI makes capable engineers faster. It does not replace the understanding that makes contributions trustworthy.

Every pull request is reviewed by a human. Submitting work you do not understand shifts that burden onto maintainers. That is not how we operate.

## New Contributors

Use AI to learn the codebase. Read the code it generates. Run it. Break it. Then submit work that reflects your understanding. We will help you through review -- that deal only works if the code is yours.
Loading