Phase 2: Find and Rules
Both tabs were rendering Explore. They now take the whole screen, as the
reference does by hiding the unit on each: a list or a page of prose
wants the room more than the panel does. Picking a search result jumps
to Explore with that combo lit, which is the reference's data-go.
The rules text is extracted verbatim rather than retyped or reworded.
Two bugs on the way there, both worth recording:
- RULES is a template literal that itself contains template literals
inside a ${} interpolation, so the bracket scanner used for the combo
table stops at the first inner backtick. The nine title and body pairs
are matched out of the source instead, with a count assertion so drift
fails loudly rather than silently dropping a rule.
- PADMAP was read with g() but never added to the list of declarations
evaluated into the vm, so it was simply not in scope.
The extractor now asserts 9 rules and 4 pad-map rows alongside the 159
combos.
141 tests, typecheck clean. Both tabs verified by rendering them, Find
with a seeded query since headless Firefox cannot type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
42
src/App.tsx
42
src/App.tsx
@@ -20,10 +20,13 @@ import {
|
||||
orderLayerKeys,
|
||||
} from "./domain/layers";
|
||||
import type { ContextCode, ControlId, Entry } from "./domain/types";
|
||||
import { searchEntries } from "./domain/search";
|
||||
import { DrillCard } from "./ui/DrillCard";
|
||||
import { DrillStrip } from "./ui/DrillStrip";
|
||||
import { FindPanel } from "./ui/FindPanel";
|
||||
import { InfoCard } from "./ui/InfoCard";
|
||||
import { Panel } from "./ui/Panel";
|
||||
import { RulesPanel } from "./ui/RulesPanel";
|
||||
import { Sheet, SheetOption } from "./ui/Sheet";
|
||||
import { drillReducer, initialDrillState } from "./ui/drillReducer";
|
||||
import { loadState, saveState } from "./ui/persistedState";
|
||||
@@ -71,6 +74,18 @@ export function App() {
|
||||
const [layer, setLayer] = useState("");
|
||||
const [selected, setSelected] = useState<Entry | null>(null);
|
||||
|
||||
// --- Find ---
|
||||
const [query, setQuery] = useState("");
|
||||
const results = useMemo(() => searchEntries(ENTRIES, query), [query]);
|
||||
|
||||
/** Jump from a search result to that combo, lit on the panel. */
|
||||
function goTo(entry: Entry) {
|
||||
setCtx(entry.ctx[0] ?? "S");
|
||||
setLayer(entry.key);
|
||||
setSelected(entry);
|
||||
setTab("Explore");
|
||||
}
|
||||
|
||||
const counts = useMemo(() => layerCounts(ENTRIES, ctx), [ctx]);
|
||||
const layers = useMemo(() => orderLayerKeys(counts.keys()), [counts]);
|
||||
const activeLayer = counts.has(layer) ? layer : "";
|
||||
@@ -121,6 +136,33 @@ export function App() {
|
||||
}, [drill.card, drill.answered]);
|
||||
|
||||
const isDrill = tab === "Drill";
|
||||
// Find and Rules take the whole screen: the reference hides the unit on both,
|
||||
// and a list or a page of prose wants the room more than the panel does.
|
||||
const isPanelTab = tab === "Explore" || isDrill;
|
||||
|
||||
if (!isPanelTab) {
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
{tab === "Find" ? (
|
||||
<FindPanel query={query} results={results} onQuery={setQuery} onPick={goTo} />
|
||||
) : (
|
||||
<RulesPanel />
|
||||
)}
|
||||
|
||||
<nav className={styles.nav}>
|
||||
{TABS.map((name) => (
|
||||
<button
|
||||
key={name}
|
||||
className={name === tab ? styles.tabOn : styles.tab}
|
||||
onClick={() => setTab(name)}
|
||||
>
|
||||
{name.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
|
||||
Reference in New Issue
Block a user