Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,15 @@ npm run validate

## Documentation

- [Backend Architecture](docs/backend.md) - Server, CDP, and module structure.
- [Architecture Map](docs/architecture.md) - Runtime boundaries and source entry points.
- [Domain Language](docs/glossary.md) - Shared project terminology.
- [Design Decisions](docs/decisions.md) - Choices, alternatives, and trade-offs.
- [Build Guide](docs/build.md) - Building from source for all platforms.
- [Cheats Guide](docs/cheats.md) - Writing and registering cheats.
- [Cheats Guide](docs/cheats.md) - Usage safety and contributor entry points.
- [CLI Reference](docs/cli.md) - Console commands and autocomplete.
- [Configuration](docs/config.md) - Config files, schema, and validation.
- [Configuration](docs/config.md) - Applying settings, saving overrides, and safety.
- [Platforms](docs/platforms.md) - Steam, web, and OS-specific setup.
- [Web UI](docs/ui.md) - VanJS dashboard and components.
- [Account Pages](docs/account-pages.md) - Editing safety and source entry points.

## Contributing

Expand Down
266 changes: 13 additions & 253 deletions docs/account-pages.md
Original file line number Diff line number Diff line change
@@ -1,258 +1,18 @@
# Account pages playbook
# Account pages

Guide for building and maintaining Account feature tabs.
Account editors can change progression and other save-sensitive values. Check the field's meaning and warning
before writing. An inferred label is not proof that a value is safe to change.

This document covers the refactored Account tab system in `src/ui/components/views/account/`. It is for new top-level Account tabs, world tabs, nested world panels, and world sub-tabs. It does not cover raw `OptionsListAccount` schema/editor changes.
For terminology, see the [glossary](glossary.md). For the rendering trade-off, see
[preserve editable row identity](decisions.md#preserve-editable-row-identity).

## 1) Source of truth
## Where to start

Read these files before adding or changing an Account feature tab:
- [Account.js](../src/ui/components/views/Account.js): Account navigation.
- [Account feature directory](../src/ui/components/views/account/): world and feature editors.
- [Shared components](../src/ui/components/views/account/components/): editable rows and page controls.
- [accountShared.js](../src/ui/components/views/account/accountShared.js): shared read/write helpers.
- [Account schema](../src/ui/config/optionsAccountSchema.json): raw option labels and warnings.

- `src/ui/components/views/Account.js`
Top-level Account shell, `ACCOUNT_TABS`, and lazy top-level pane mounting.
- `src/ui/components/views/account/W1Tab.js` ... `W7Tab.js`
World tab shells and sub-tab registries.
- `src/ui/components/views/account/tabShared.js`
Shared world/nested tab factories, tab navigation, lazy panes, and persistent panes.
- `src/ui/components/views/account/accountLoadPolicy.js`
`useAccountLoad()` for standardized load state and load failure logging.
- `src/ui/components/views/account/accountShared.js`
Shared value helpers, Haxe unwrapping, verified writes, bulk writes, stable state helpers, and write status.
- `src/ui/components/views/account/components/`
Shared chrome, rows, sections, page shells, and collection helpers.

Current structure:

- Top-level Account tabs include Account Options, Upgrade Vault, and W1-W7.
- W1-W7 contain implemented feature tabs.
- W2 has a nested Alchemy panel: Brewing, Liquid, Vials, Pay 2 Win, Sigils.
- W3 has a nested Construction panel: Buildings, Cogs.

## 2) Shared contracts

### Page chrome

- Prefer `PersistentAccountListPage(...)` for most editable Account features.
- Use `RefreshButton`, `WarningBanner`, `NoticeBanner`, `AccountSection`, and `AccountRow` before adding tab-local chrome.
- Keep tab-specific selectors out of shared CSS unless they define a reusable primitive.

### Loading

- Use `useAccountLoad({ label })` for account-page reads.
- Keep reads inline in the tab. The shared hook owns state transitions and logging.
- Call `load()` once when the component is constructed. Because Account panes lazy-mount, this loads the tab only when first opened.
- Use `Promise.all` for independent reads.
- Normalize indexed game payloads with `toIndexedArray(raw)` from `src/ui/utils/index.js`.
- Use `readLevelDefinitions(...)` when pairing a GGA levels array with a `cList` definition table.

### Writes

- Use `useWriteStatus()` for row-level and bulk write actions.
- Use `writeVerified(path, value)` when one GGA write must be confirmed.
- Use `writeManyVerified(writes)` for custom batches.
- Use `runBulkSet(...)` when many rows share a target-value and local-state update flow.
- Use shared row/action components so loading, success, and error states render consistently.
- Do not hand-roll status timers. `useWriteStatus()` owns the success/error clear timing.

### Feedback classes

- Current shared Account rows emit `account-row--success` and `account-row--error`.
- Action buttons use the shared button/status classes from `ActionButton`.
- Do not introduce old `feature-row--success`, `feature-row--error`, or tab-local feedback class contracts.

## 3) Pattern selection

Pick one rendering pattern per tab and stay consistent within that tab.

### Pattern A: Persistent list page

Use this for editable row tabs, dense lists, bulk actions, and any UI where input focus, row status, or scroll position needs to survive writes.

Typical examples:

- `w1/AnvilTab.js`
- `w2/VialTab.js`
- `UpgradeVaultTab.js`

Shape:

```js
export const MyFeatureTab = () => {
const { loading, error, run } = useAccountLoad({ label: "My Feature" });
const listNode = div({ class: "account-list" });

const load = async () =>
run(async () => {
// Read game data, normalize it, then update existing state.
});

load();

return PersistentAccountListPage({
title: "MY FEATURE",
description: "Short operational description.",
actions: RefreshButton({ onRefresh: load }),
state: { loading, error },
loadingText: "READING MY FEATURE",
errorTitle: "MY FEATURE READ FAILED",
initialWrapperClass: "account-list",
body: listNode,
});
};
```

### Pattern B: Simple rebuild body

Use when the tab is small, mostly read-only, and remounting content after load is acceptable.

Pass a small reactive `body` to `PersistentAccountListPage` when the surrounding page should keep persistent Account chrome.

### Pattern C: Cached collection or card UI

Use when the data shape can change but existing rows or cards should survive writes and refreshes.

Typical techniques:

- `createIndexedStateGetter()` for sparse numeric/list index state.
- `getOrCreateState(map, key, initial)` for keyed row/card state.
- `createStaticRowReconciler(container)` when rows should rebuild only after a signature changes.
- Stable arrays of row/card nodes when refreshing values should not rebuild UI identity.

## 4) Shared UI primitives

Use these before creating new one-off helpers:

- `EditableNumberRow`
Focus-safe numeric row. Keeps committed value state separate from draft input text.
- `ClampedLevelRow`
Thin adapter for single-path level fields with min/max clamping.
- `BulkActionBar`, `SetAllNumberControl`, `SetAllSelectControl`
Header action strip and set-all controls.
- `AddFromListSection`
Add-from-dropdown collection section.
- `RemovableStoredRow`
Removable collection row with row-local write status.
- `AccountRow`
Non-numeric row shell with status-aware classes.
- `AccountSection`
Standard grouped section header/body.

Do not extract tiny one-off helpers when the logic is only a couple of lines and used once. Inline the logic unless a helper materially improves reuse or readability.

## 5) Reactivity safety

The most common regression is remounting rows or cards after a value changes. That can lose focus or hide success/error feedback.

Rules:

- Keep VanJS reactive function scope as small as possible.
- Do not return arrays directly from reactive children; return one node or wrap multiple nodes in a container.
- Do not wrap an input in a reactive block that depends on that input's value.
- Do not read mutable `state.val` while constructing row or card components inside a reactive list renderer if that subscribes the parent renderer.
- Build persistent rows or cards once where possible, then update backing `van.state` values in place.
- For dynamic lists, rebuild only when the list shape changes, not when an individual row value changes.

Bad:

```js
const Row = ({ valueState }) => {
const inputValue = van.state(String(valueState.val ?? 0));
return input({ value: inputValue });
};
```

Good:

```js
const Row = ({ valueState }) => {
const inputValue = van.state("0");

van.derive(() => {
inputValue.val = String(valueState.val ?? 0);
});

return input({ value: inputValue });
};
```

Prefer `EditableNumberRow` when this pattern fits; it already handles draft text and focus-safe syncing.

## 6) Wiring new tabs

### Add a feature tab under an existing world

1. Create `src/ui/components/views/account/wN/MyFeatureTab.js`.
2. Import it in `src/ui/components/views/account/WNTab.js`.
3. Add a `WN_SUBTABS` entry with a stable kebab-case `id`, uppercase `label`, and `component`.
4. If the world has a nested panel, add the tab to that nested registry instead of the outer world registry.
5. Add feature CSS at `src/ui/styles/tabs/wN/_my-feature.css` if needed.
6. Import that CSS from `src/ui/styles/tabs/wN/_index.css`.

### Add a nested panel tab

1. Find the nested registry, such as `ALCHEMY_SUBTABS` in `W2Tab.js` or `CONSTRUCTION_SUBTABS` in `W3Tab.js`.
2. Add the new tab to that nested array.
3. Keep the registry wired through the existing `createNestedTab(...)` component.
4. Keep panel-specific CSS near the world's existing tab CSS.

### Add a top-level Account tab

1. Create `src/ui/components/views/account/MyTopLevelTab.js`.
2. Import it in `src/ui/components/views/Account.js`.
3. Add an `ACCOUNT_TABS` entry with `isWorld: false`.
4. Add styles only if the shared Account primitives are insufficient.

### Add a new world tab

1. Create `src/ui/components/views/account/WNTab.js`.
2. Register it in `src/ui/components/views/Account.js` with `isWorld: true` and `worldNum`.
3. Define its sub-tab registry and export it with `createWorldTab(...)`.
4. Add world-specific styles and imports.

## 7) CSS rules

- Shared Account primitives live in `src/ui/styles/_account-pages.css`.
- Top account/world navigation lives in `src/ui/styles/_world-tabs.css`.
- Feature-specific CSS belongs in `src/ui/styles/tabs/wN/_feature-name.css`.
- Import feature CSS from the matching `src/ui/styles/tabs/wN/_index.css`.
- Use existing classes first: `tab-container`, `scroll-container`, `account-list`, `account-row`, `account-section`, `account-header__actions`, `account-setall-row`, `warning-banner`, `tab-add-row`.
- Keep dimensions stable for row controls, cards, grids, status labels, and action buttons so writes do not shift layout.

## 8) Validation checklist

Run the full validation when code changes:

```powershell
npm run validate
```

If the change is narrow and full validation is too expensive, run the tightest checks that cover changed files:

```powershell
node --check src/ui/components/views/account/wN/MyFeatureTab.js
npx eslint src/ui/components/views/account/ src/ui/styles/
```

Manual checks:

- The tab appears at the correct nav level.
- Lazy mounting loads the tab only when first opened.
- Initial loading and initial failure states use the shared page chrome.
- Refresh reloads data without destroying stable rows unnecessarily.
- Write actions show loading, success, and error feedback on the correct row/action.
- Changed and unchanged verified writes both show success feedback.
- Inputs keep focus while typing and do not reset on each keystroke.
- Bulk actions use `useWriteStatus()` and verified writes.
- Placeholder tabs remain passive.
- CSS is imported and scoped to the feature/world.

## 9) Anti-patterns

- Importing or referencing removed `featureShared.js` helpers.
- Manual load-state boilerplate when `useAccountLoad()` fits.
- Manual status timers instead of `useWriteStatus()`.
- Rebuilding rows/cards on every value write.
- Constructor-time reads of mutable `*.val` in row/card builders when they cause parent remounts.
- Returning arrays from reactive VanJS children.
- Putting feature-specific selectors in shared Account CSS.
- Adding a new helper for logic that is short, local, and used once.
Read the relevant feature and shared component before changing an editor. Their source owns the rendering
and write contracts.
32 changes: 32 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Architecture map

Use this map to choose where to start reading. Source owns implementation details and contracts.
See the [glossary](glossary.md) for terminology and [decisions](decisions.md) for design rationale.

## Runtime boundaries

| Area | Responsibility | Start here |
| ------------------ | --------------------------------------------------------- | ----------------------------------------------------- |
| Node process | Orchestration, platform attachment, local server, and CLI | [main.js](../src/main.js), [modules](../src/modules/) |
| Injected game code | Commands and hooks operating in the game runtime | [cheats/main.js](../src/cheats/main.js) |
| Web UI | User interaction with the injector | [App.js](../src/ui/components/App.js) |

The Node process connects to the game through CDP. The Web UI communicates with the Node server.
Injected code runs in the game context; Node and UI code have separate environments.

## Find the relevant area

| Task area | Source |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Platform attachment and injection | [game modules](../src/modules/game/) |
| Configuration loading | [configManager.js](../src/modules/config/configManager.js) |
| HTTP and WebSocket contracts | [apiRoutes.js](../src/modules/server/apiRoutes.js), [wsServer.js](../src/modules/server/wsServer.js) |
| Console interaction | [cliInterface.js](../src/modules/cli/cliInterface.js) |
| Command registration and state | [cheats/core](../src/cheats/core/) |
| Commands and game hooks | [cheats/cheats](../src/cheats/cheats/), [proxies](../src/cheats/proxies/) |
| UI workspaces and shared components | [views](../src/ui/components/views/), [components](../src/ui/components/) |
| UI state and communication | [state](../src/ui/state/), [services](../src/ui/services/) |
| Styles | [style.css](../src/ui/entry/style.css), [styles](../src/ui/styles/) |
| Bundling and packaging | [rollup.config.mjs](../rollup.config.mjs), [package.json](../package.json) |

See the [Account page guide](account-pages.md) and [cheats guide](cheats.md) for those areas.
Loading