Default Styles: the type-less Default tier
- Dominant language
- TypeScript
- Stars
- 481
- Forks
- 108
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 5
Description
## Parent
- #1881
## What to build
The **Default Styles** feature itself — the type-less global Default tier, layered onto the rename, preview, and form module from the prior slices.
A user can set one global node style and one global edge style that apply to every type as a baseline, edit them from a dedicated **Default styles** row at the top of the styles sidebar, save them inside a styles file, and choose whether to take an incoming default when loading someone else's file.
- **Cascade**: extend the resolve function to the three-layer model — `{ ...appDefault, ...userDefault, ...userCustom.get(type) }` — via an options object of layers so merge logic stays out of the atom getter. Any per-type value beats the type-less default; within each layer only specified fields override (partial patch).
- **Atoms**: two new single-object storage atoms — `userDefaultVertexStyleAtom` / `userDefaultEdgeStyleAtom` (keys `user-default-vertex-style` / `user-default-edge-style`). Typed `Partial` / `Partial`. No `reconcileMapByKey` — single objects, blind overwrite.
- **Default styles dialog**: a second instantiation of the form module with a visual-only schema, writing to the user default atoms, reached from a dedicated **Default styles** row at the top of the styles sidebar (preview swatch + subtitle + edit button, seated first with a divider). Its destructive action is labelled **Clear Customization**, matching the per-type dialog, and clears just the user default.
- **File format**: optional `defaults: { vertex?, edge? }` section in the styles file. Save writes the user default. Parser stays whole-file atomic; default entries reuse the per-type visual schema with attribute/label fields omitted; default-tier issues carry a `"(default)"` sentinel in `typeName`. Old files without `defaults` still load.
- **Selective import**: the incoming default appears in the import modal as its own before-and-after card the user can select or leave alone — this is what keeps loading non-destructive now that there is no shared layer to absorb it. The import item union gains two type-less kinds beside the existing vertex and edge kinds, carrying no type and a `Partial<*VisualStyle>` payload, so the default's different write target (a single object rather than a Map entry) stays visible in the discriminant instead of being smuggled through a sentinel type. The apply step gains matching branches writing the single-object atoms. Default cards sit first in the grid, mirroring the sidebar. Before/After preview labels and identical-style exclusion apply unchanged, so an incoming default matching the current one drops out of the list.
- **Settings copy**: the bulk clear becomes **Clear All Style Customizations** with a **Clear All** button (it was "Reset to Defaults", which turns ambiguous once "default styles" names the user's tier). It clears per-type styles and the user default together; the confirmation prose names both and says appearance returns to the app defaults.
- **Glossary**: update the **Styles** entry with the three-layer cascade and the Default styles term; amend the existing type-keyed-map-atoms ADR to note the default's single-object storage. No new ADR — the cascade carries no surprise worth one now that the shared Source is gone.
- **Tests**: the cascade matrix (specificity, partial patch, full three-layer stack), clear semantics, file round-trip for `defaults`, and the import plan and apply steps for default items. Extend `DbState` with the two new atoms.
## Acceptance criteria
- [ ] Setting a default styles every type as a baseline; a per-type style overrides it
- [ ] Only fields a per-type style specifies override the default (partial patch)
- [ ] The Default styles dialog edits visual fields only, is reachable from the top of the styles sidebar, and its Clear Customization clears just the user default
- [ ] A per-type Clear Customization falls through to the user default, then the app default
- [ ] A styles file carries the default; save→load reproduces it; old files without `defaults` still load; an invalid default value rejects the whole file with a `"(default)"`-scoped issue
- [ ] An incoming default appears as its own card in the import modal, seated first, with Before/After previews
- [ ] Leaving the incoming default unselected leaves the user's own default untouched; selecting it overwrites it
- [ ] An incoming default identical to the current one is excluded from the modal
- [ ] Settings shows **Clear All Style Customizations** / **Clear All** and clears per-type styles and the user default together
- [ ] The Styles glossary entry describes the three-layer cascade; the type-keyed-map-atoms ADR notes single-object default storage
- [ ] Cascade, clear, round-trip, and import plan/apply tests pass; `DbState` extended
- [ ] `pnpm checks` and `pnpm test` pass
## Blocked by
- #1883 (style dialog form-schema foundation — the Default dialog is a second instantiation of that module)
> [!IMPORTANT]
> Internal only — this issue is maintained by the core team and is not accepting external contributions.
Contributor guide
Research direction
Begin with blocked issue #1883 and trace the styles sidebar, form module, cascade resolver, styles-file parser, import modal, and DbState entries described here. Use the listed cascade, clear, round-trip, and import tests as the completion guide, then run pnpm checks and pnpm test; this issue is marked internal-only and is not accepting external contributions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- documentation, frontend, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 20/100