141 lines
9.0 KiB
Markdown
141 lines
9.0 KiB
Markdown
<!-- 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 -->
|
||
|
||
<!-- ASTRYX:START -->
|
||
Astryx v0.2.0 · 154 components
|
||
CLI: run every command as `npx astryx <cmd>` (shown below as `astryx ...`).
|
||
|
||
SETUP (once, in your app entry e.g. main.tsx) — without these, components render unstyled:
|
||
import "@astryxdesign/core/reset.css";
|
||
import "@astryxdesign/core/astryx.css";
|
||
|
||
WORKFLOW — discover, don't guess. Before writing UI:
|
||
1. `astryx build "<idea>"` — START HERE: returns a kit (closest [page] + [block]s + [component]s). No args = full playbook.
|
||
2. `astryx template <name> [--skeleton]` — scaffold the [page]/[block]s it named, or study their layout. Templates are reference code.
|
||
3. `astryx component <Name>` — props + examples for every component you use.
|
||
|
||
RULES:
|
||
- No <div> — components do all layout/spacing. Full page → AppShell; sidebar nav → SideNav.
|
||
- Frame first: pick the shell (AppShell / Layout+LayoutPanel) and budget regions in px BEFORE writing content (`astryx docs layout`).
|
||
- Dense data = rows (Table, List/Item) edge-to-edge — never Card-wrapped list items. Card = dashboard widgets, galleries, settings groups only.
|
||
- Status → StatusDot/Token; Badge only for counts and enumerated states, never decoration.
|
||
- Custom styling: component props first; else Tailwind utilities backed by tokens (bg-surface, text-primary, rounded-lg) via tailwind-theme.css. No raw hex/px.
|
||
- Tokens for every value (`astryx docs tokens`). Brand/accent via `astryx theme` — never override --color-* in :root.
|
||
- SELF-CHECK before you finish: re-read the file and replace any style={{…}}, raw <div>/<span> layout, imported .css/@apply, or hardcoded/arbitrary value (e.g. bg-[#fff], p-[13px]) with the component or a token-backed utility. If unsure a component/prop exists, run `astryx component <Name>` / `astryx search "<thing>"`; don't hand-roll CSS.
|
||
|
||
MORE CLI:
|
||
search "<query>" find any component / hook / doc / template / block
|
||
component --list 154 components by category
|
||
template --list page + block recipes
|
||
docs <topic> color, elevation, icons, illustrations, internationalization, layout, migration, motion, principles, shape, spacing, styling, theme, tokens, typography
|
||
swizzle <Name> eject component source for deep customization
|
||
upgrade --apply run after any @astryxdesign/core bump
|
||
<!-- ASTRYX:END -->
|
||
|
||
<!-- LOYALY:START -->
|
||
# Loyaly Merchant Dashboard — project rules
|
||
|
||
## Toolchain
|
||
- **Node 22 is required** (`.nvmrc` → 22.23.1). The astryx CLI hard-fails on Node 20.
|
||
`nvm use` before running any `npx astryx` command or `npm run theme:build`.
|
||
- Dev server runs on **port 3100** (`lytsup-site` owns 3000).
|
||
|
||
## Theme
|
||
- All design values live in `src/theme/loyalyTheme.ts`. Edit that, then run
|
||
`npm run theme:build`, then **commit the generated** `src/theme/loyaly.{css,js,d.ts,variants.d.ts}`.
|
||
- `theme:build` is deliberately NOT wired to `prebuild` — it would break `npm run build` on Node 20.
|
||
- Import the theme from **`@/theme`**, never `@/theme/loyaly` directly. `@/theme` re-attaches
|
||
the Lucide icon registry, which `astryx theme build` cannot serialise (icons are React
|
||
components). Skipping it silently degrades every `<Icon>` to Astryx's default glyph set.
|
||
- `globals.css` imports `../theme/loyaly.css`, NOT `@astryxdesign/theme-neutral/theme.css`.
|
||
Astryx theme CSS is `@scope`-gated on the theme *name*, so neutral's rules match nothing
|
||
under `[data-astryx-theme="loyaly"]`.
|
||
|
||
## The monochrome rule
|
||
Gray is the accent. The **only** colour permitted is semantic: success, warning, error.
|
||
- Astryx's categorical hues (blue/cyan/teal/purple/pink/orange) are aliased to gray in
|
||
`aliasHueToGray()`. green/red/yellow deliberately keep hue — Banner's semantic statuses
|
||
resolve *through* those hue tokens.
|
||
- Some neutral-theme component styles hardcode colour instead of reading a token
|
||
(`badge variant:info`, `progressbar variant:accent`). Those need an explicit `components`
|
||
override; `aliasHueToGray()` cannot reach them.
|
||
- `/tokens` (dev-only route) is the audit page. After any theme change, load it and run the
|
||
chromatic sweep in the browser console — it should report **0** non-semantic colour nodes.
|
||
|
||
## Known upstream issues (Astryx 0.2.0)
|
||
- **`useEntryAnimation` breaks SSR hydration.** Its source assumes `'use client'` modules never
|
||
run on the server; in the App Router they do. Any `FieldStatus` (i.e. `TextInput`/`TextArea`
|
||
`status={...}`) present at *initial paint* renders without the slide-down class on the server
|
||
and with it on the client → an unpatchable class mismatch. Setting status on blur/submit
|
||
(the normal path) is unaffected. Don't server-render a field that already has a status.
|
||
- Component keys in `defineTheme({components})` are inconsistently cased: `text-input` but
|
||
`progressbar`. The CLI warns on unknown keys, but that warning is **not** reliable in
|
||
either direction:
|
||
- A *misspelled* key (`textinput`) is dropped silently — the CSS is never emitted.
|
||
- A key the CLI doesn't know but Astryx *does* use (`table-cell`, `table-header-cell`)
|
||
warns yet still emits correctly.
|
||
So never trust the warning alone. After any `components` change, grep the generated
|
||
`src/theme/loyaly.css` for the rule, then confirm the computed style in the browser.
|
||
Valid targets are whatever `themeProps('...')` is called with in `dist/<Component>/*.js`.
|
||
- `StyleOverrides` supports structural pseudo-classes, not just interaction ones —
|
||
`':first-child'` emits correctly (used for the table first-column lead-in).
|
||
|
||
## Deployment — never ship `.next/` wholesale
|
||
This filled the production disk and took the server down once. A working tree's `.next/`
|
||
reaches **2+ GB**, but almost none of it is runtime state:
|
||
|
||
| path | size (typical) | ships? |
|
||
|---|---|---|
|
||
| `.next/dev` | 1.8 GB | **no** — Turbopack dev-server cache from `npm run dev` |
|
||
| `.next/cache` | 120–160 MB | **no** — incremental build cache, build host only |
|
||
| `.next/server`, `types`, `trace` | ~24 MB | **no** — inputs to the standalone trace, not read at runtime |
|
||
| `.next/standalone` | 54 MB | **yes** — server.js + traced node_modules |
|
||
| `.next/static` | 3 MB | **yes** — hashed client assets (nginx serves at `/_next/static/`) |
|
||
| `public` | 344 KB | **yes** |
|
||
|
||
Those last three are the entire payload: **~55 MB unpacked, 18 MB gzipped.**
|
||
- Non-Docker deploy: `npm run bundle` → `dist/loyaly-mer-login.tar.gz`. Never `scp -r .next`.
|
||
- Docker: `.dockerignore` excludes `node_modules` and `.next`. **Do not delete it** — without
|
||
it `COPY . .` pushes the host's 2 GB `.next` and 639 MB `node_modules` into the build
|
||
context, including darwin-arm64 sharp binaries that are wrong for Alpine.
|
||
- `npm run clean` drops `.next`, `dist` and the tsbuildinfo when the tree gets heavy.
|
||
|
||
**`CI_BUILD=1` in container builds.** Next 16.3 enables `turbopackFileSystemCacheForBuild`
|
||
by default; it writes 117 MB to `.next/cache` that only pays off when that directory is
|
||
restored between builds. Docker/Dokploy starts from a clean layer every time, so it is
|
||
written and never read. The Dockerfile sets `CI_BUILD=1`, which flips the flag off in
|
||
`next.config.ts`: **~20% less build CPU** (32s vs 40–43s measured) and `.next` drops
|
||
202 MB → 86 MB. Leave it unset locally — repeat `npm run build` there does reuse the cache.
|
||
It changes no output: the deployable payload is 58 MB either way.
|
||
|
||
## Conventions
|
||
- Follow the Astryx rules above: no raw `<div>` for layout, no hardcoded hex/px, component
|
||
props first then token-backed Tailwind utilities.
|
||
- `src/lib/icons.ts` is the single icon map. Only the 26 semantic names resolve via
|
||
`<Icon icon="search"/>`; everything else is `<Icon icon={ICONS.stores}/>`. Never import a
|
||
lucide icon directly into a feature file.
|
||
|
||
## Settings spacing contract
|
||
Every Settings screen renders inside `src/features/settings/SettingsPage.tsx`. Do not add
|
||
page padding in an individual settings page — change the container instead.
|
||
|
||
| | value | why |
|
||
|---|---|---|
|
||
| Horizontal | 32px (`paddingInline={8}`) | clears the sub-nav and the Loyaly AI rail |
|
||
| Top | 32px (`paddingBlock={8}`) | title never touches the header |
|
||
| Bottom | 48px (`className="pb-12"`) | last card is never flush to the fold |
|
||
| Header → body | 32px (`gap={8}`) | title block reads as its own layer |
|
||
| Between body blocks | 24px (`gap={6}`) | cards, tables, toolbars |
|
||
|
||
`SpacingStep` stops at 10 (40px) and Stack has no `paddingBlockEnd`, which is why the 48px
|
||
bottom goes through the Tailwind bridge — `pb-12` still resolves to `--spacing-12`, so no
|
||
raw pixel value enters the codebase. It sits in the `utilities` layer, which the cascade
|
||
puts after `astryx-base`, so it wins over `paddingBlock` without `!important`.
|
||
|
||
Card padding (24px) and table cell padding (12px, 20px first-column lead-in) are set once
|
||
in `loyalyTheme.ts` so every module agrees — not per page.
|
||
<!-- LOYALY:END -->
|