Document the full-set searchable Font Awesome picker (task e2fc2dbc) (#292)

Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com>
Co-committed-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #292.
This commit is contained in:
2026-09-30 19:16:39 -04:00
committed by approver_agent
parent 966beb4862
commit b629251905
9 changed files with 47 additions and 28 deletions
+5 -5
View File
@@ -1,8 +1,8 @@
# Third-party notices
NestQuest bundles glyph data from the icon sets below. The curated names are listed in
`tools/icons/curated-icons.json`; `tools/icons/generate.mjs` reads each glyph's SVG path
data from the locally installed packages and generates it into both bundles
NestQuest bundles glyph data from the icon sets below. The curated Lucide names and the
Font Awesome quick-pick names are listed in `tools/icons/curated-icons.json`;
`tools/icons/generate.mjs` reads each glyph's SVG path data from the locally installed packages and generates it into both bundles
(`admin/src/icons/registry.generated.ts` and `frontend/src/icons/registry.generated.ts`).
Nothing is loaded from a CDN or fetched at runtime. The package versions used are recorded
in the header of each generated file.
@@ -15,8 +15,8 @@ in the header of each generated file.
- Icons: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
- Fonts: [SIL OFL 1.1](https://openfontlicense.org)
- Code: [MIT](https://opensource.org/licenses/MIT)
- Use in NestQuest: a curated subset of the Free **solid** icon set, embedded as SVG path
data. No fonts are bundled.
- Use in NestQuest: the full Free **solid** icon set, embedded as SVG path data (a curated
subset doubles as the admin picker's quick picks). No fonts are bundled.
Font Awesome Free by @fontawesome — https://fontawesome.com — icons licensed under CC BY 4.0.
@@ -1355,7 +1355,7 @@ describe("DefinitionsTab Font Awesome and emoji icons", () => {
fireEvent.click(within(s).getByRole("button", { name: "Morning" }));
}
it("ships the curated Font Awesome subset, each drawn as a filled glyph", () => {
it("ships the curated Font Awesome quick picks, each drawn as a filled glyph", () => {
expect(FA_DEFINITION_ICONS.length).toBeGreaterThan(0);
for (const entry of FA_DEFINITION_ICONS) {
expect(entry.kind).toBe("fa");
+12 -8
View File
@@ -1,11 +1,14 @@
/**
* The icons a definition's `icon` may name (ADMIN-SPEC §3.3): the curated
* Lucide set, the curated Font Awesome Free solid subset, or an emoji. The
* names, labels and path data come from the shared icon registry
* (`src/icons/registry.generated.ts`, generated from
* `tools/icons/curated-icons.json`) — no CDN, no runtime fetch. The stored
* value is namespaced — `lucide:<name>`, `fa:<name>` or `emoji:<grapheme>` —
* and a legacy bare `<kebab-name>` still names the same Lucide entry.
* Lucide set, any icon in the full Font Awesome Free Solid set, or an emoji.
* The Font Awesome quick picks (`FA_DEFINITION_ICONS`) are only a curated
* shortcut list; `fa:<name>` resolves against the full set
* (`FA_SOLID_ICONS`). The names, labels and path data come from the shared
* icon registry (`src/icons/registry.generated.ts`, generated by
* `tools/icons/generate.mjs` from `tools/icons/curated-icons.json` and the
* installed Free Solid package) — no CDN, no runtime fetch. The stored value
* is namespaced — `lucide:<name>`, `fa:<name>` or `emoji:<grapheme>` — and a
* legacy bare `<kebab-name>` still names the same Lucide entry.
*/
import { GlyphSvg } from "../icons/GlyphSvg";
import {
@@ -105,8 +108,9 @@ export function emojiIconText(value: string | null): string {
}
/**
* The curated glyph (Lucide or Font Awesome) an icon value resolves to via
* the shared parser; emoji, unknown and null values give null.
* The glyph an icon value resolves to via the shared parser: a curated
* Lucide entry or any Font Awesome Free Solid icon (quick pick or not);
* emoji, unknown and null values give null.
*/
export function findDefinitionIcon(value: string | null): DefinitionIconEntry | null {
const icon = resolveIcon(value);
+1 -1
View File
@@ -157,7 +157,7 @@ describe("offline bundling", () => {
}
});
it("no bundled source imports an icon package — only the curated generated data ships", () => {
it("no bundled source imports an icon package — only the generated registry data ships", () => {
const sources = [join(REPO_ROOT, "admin", "src"), join(REPO_ROOT, "frontend", "src")]
.flatMap(filesUnder)
.filter((path) => /\.(ts|tsx)$/.test(path) && !path.endsWith(".test.ts") && !path.endsWith(".test.tsx"));
+16 -5
View File
@@ -105,14 +105,25 @@ Each definition carries one optional icon, shown on both surfaces. The picker of
sources:
- **Lucide** — a grid of the curated Lucide glyphs, drawn stroked.
- **Font Awesome** — a grid of a curated subset of the Font Awesome Free *solid* set,
drawn filled.
- **Font Awesome** — a search over the **full** Font Awesome Free *solid* set (every
glyph in `@fortawesome/free-solid-svg-icons`), plus a curated quick-pick grid, all drawn
filled. With the search field empty the quick-pick grid shows; typing filters the full
set by name or label (an exact name ranks first, then name prefixes, then the rest),
shown 40 results per page with *Previous* / *Show more*.
**Free-text rule:** an admin may type a Free Solid icon name directly (trimmed,
case-insensitive — `Dog` stores `fa:dog`). Text that is not the exact name of a Free
Solid icon — a partial search, a misspelling, or a Pro or Brands name — is rejected in
the picker with an inline error and cannot be saved until a result is selected or the
full name is typed.
- **Emoji** — a text input; the typed emoji is stored as entered (not trimmed) and must be
1–8 code points with no whitespace, control characters, `<`, `>` or `&`.
Both curated lists come from `tools/icons/curated-icons.json`; their glyph path data is
generated into the admin and panel bundles by `tools/icons/generate.mjs` (no CDN, no
runtime fetch). See `THIRD-PARTY-NOTICES.md` for licences.
The curated Lucide set and the Font Awesome quick-pick list come from
`tools/icons/curated-icons.json`; `tools/icons/generate.mjs` generates their glyph path
data, together with the full Font Awesome Free Solid set, into the admin and panel bundles
(no CDN, no runtime fetch). Embedding the full set makes both bundles heavier — the panel
bundle (`nestquest-cards.js`) is about 1 MB raw / 276 kB gzip. See
`THIRD-PARTY-NOTICES.md` for licences.
The stored value is a **namespaced key**:
+2 -2
View File
@@ -78,6 +78,6 @@ The panel speaks tabletop; the admin speaks plainly. Both sit on the same `quest
decide before shipping the card.
- No gamification is in scope. XP, levels, streaks and rarity were explicitly deferred,
but the append-only event log already supports computing them later.
- Icon per quest definition is chosen by the parent from curated Lucide and Font Awesome
Free sets or as an emoji, stored as a namespaced key (`lucide:`/`fa:`/`emoji:`) —
- Icon per quest definition is chosen by the parent from the curated Lucide set, the full
Font Awesome Free Solid set (searchable, with curated quick picks) or as an emoji, stored as a namespaced key (`lucide:`/`fa:`/`emoji:`) —
resolved 2026-09-26, see `ADMIN-SPEC.md` §3.3.
+2 -1
View File
@@ -106,7 +106,8 @@ collapse the column, the three-column rhythm is the layout.
1. **Icon tile** 64×64, radius 10px, `--nq-p-icon-tile`, glyph 34px white — the
definition's icon, resolved from its namespaced key (`ADMIN-SPEC.md` §3.3):
`lucide:<name>` draws the curated Lucide glyph **stroked** (fill none, stroke-width 2,
round caps and joins); `fa:<name>` draws the curated Font Awesome glyph **filled**;
round caps and joins); `fa:<name>` draws the Font Awesome Free Solid glyph **filled** (any icon in the full
set, not only the quick picks);
`emoji:<grapheme>` renders the emoji **as text**. A legacy bare name resolves as
`lucide:<name>`. No icon, or any key that does not resolve (unknown namespace or name,
malformed emoji), falls back to the **star** glyph. Glyph data is bundled from the
+3 -2
View File
@@ -1,7 +1,8 @@
/**
* Lit rendering for the shared icon registry (`registry.generated.ts`,
* generated from tools/icons/curated-icons.json). The glyph path data is
* bundled — no CDN and no runtime fetch.
* generated by tools/icons/generate.mjs: the curated Lucide set plus the full
* Font Awesome Free Solid set). The glyph path data is bundled — no CDN and
* no runtime fetch.
*/
import { html, svg, type TemplateResult } from "lit";
+5 -3
View File
@@ -16,13 +16,15 @@
* Lucide circles, rects and lines are converted to path `d` strings so every
* glyph is plain `{ viewBox, paths }` data any renderer can draw.
*
* Besides the curated sets, the module embeds the complete Font Awesome Free
* Solid set (`FA_SOLID_ICONS`). Its source is the installed
* Besides the curated sets (the Lucide set and the Font Awesome quick picks),
* the module embeds the complete Font Awesome Free Solid set (`FA_SOLID_ICONS`),
* which is what `fa:<name>` resolves against. Its source is the installed
* @fortawesome/free-solid-svg-icons package itself: every exported icon
* definition, de-duplicated by canonical `iconName` (the package also exports
* each legacy alias, e.g. faHome for `house`, as the same definition). The
* package carries no human labels, so each label is the kebab name in Title
* Case (`spray-can-sparkles` → "Spray Can Sparkles").
* Case (`spray-can-sparkles` → "Spray Can Sparkles"). Embedding the full set
* makes both bundles heavier: the panel bundle is about 1 MB raw / 276 kB gzip.
*/
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { createRequire } from "node:module";