feat: audit phase 6 (T4) — node allocation, the Path of Exile payoff
The site borrowed PoE's visual language — passive-tree layout, plasma connections, an RPG stats panel — without any of its mechanic. Nodes were purely decorative. This is the interaction the concept was promising. - New utils/allocation.ts: path-to-root walking, allocate/deallocate, edge state, and URL encode/decode. Pure, 17 unit tests. - Allocation is path-connected like the game: allocating a leaf brings its whole path, and deallocating a node removes its descendants so nothing is ever orphaned. pathToRoot is cycle-safe, and a test asserts every real node reaches the origin. - New pixi/allocation.ts: the path reads as "lit" by dimming everything else rather than brightening the selection — the tree is already near its brightness ceiling, so recession is the only headroom. Nodes dim via alpha (nothing tweens it after the entrance); connections dim via tint, because their alpha is driven by the permanent plasma pulse and writing to it would fight the animation every frame. - Shareable via ?build=. Encoded as node ids, not indices: a bitset would be shorter, but any edit to data.ts would silently re-point every previously shared link at the wrong nodes. Decoding re-derives each path, so a hand-edited or partially-stale link can't produce a broken build, and unknown ids are dropped rather than throwing. - Allocation hangs off a button in the modal rather than the node click, which already opens that modal. BuildBar shows the count with share/reset; share falls back to rewriting the address bar when the clipboard is blocked. Two bugs found while verifying: - Both deep links (?build and ?tour) had ended up nested inside the first-visit coach-hint setTimeout, so a shared link did nothing at all for a returning visitor and waited out the whole intro for everyone else — backwards, since a shared link is precisely the cold-arrival case. Hoisted to run immediately, unconditionally. A ?tour link now also skips the intro. - A restored ?build= was wiped by the entrance, which tweens every node's alpha to 1 after the deep link applies. The result was a full-brightness tree with a counter claiming 4 allocated. Now re-applied once the entrance finishes. Verified in the production build: allocating TypeScript lights origin -> Skills -> Languages -> TypeScript and dims the rest; the counter reads 4; the generated link restores exactly that state on a cold load; Reset clears it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
aa1cda8245
commit
85086e31b2
6 files changed
+448
-10
No files matched your search
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
// Controls for the allocated path: how many nodes, share, reset.
|
||||
const {
|
||||
count = 0,
|
||||
onReset,
|
||||
onShare
|
||||
}: {
|
||||
count?: number;
|
||||
onReset: () => void;
|
||||
onShare: () => Promise<boolean> | boolean;
|
||||
} = $props();
|
||||
|
||||
let copied = $state(false);
|
||||
|
||||
async function share() {
|
||||
copied = await onShare();
|
||||
if (copied) setTimeout(() => (copied = false), 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if count > 0}
|
||||
<div class="build-bar" role="status" aria-live="polite">
|
||||
<span class="count"><strong>{count}</strong> allocated</span>
|
||||
<button onclick={share}>{copied ? '✓ Link copied' : 'Share build'}</button>
|
||||
<button class="reset" onclick={onReset}>Reset</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.build-bar {
|
||||
position: fixed;
|
||||
top: var(--space-5);
|
||||
right: var(--space-5);
|
||||
z-index: var(--z-panel-toggle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid rgba(255, 215, 0, 0.4);
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(10, 10, 20, 0.85);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.count strong {
|
||||
color: var(--color-gold);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.25rem 0.6rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: var(--radius-pill);
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
font: inherit;
|
||||
font-size: 0.72rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: var(--color-gold);
|
||||
}
|
||||
|
||||
.reset {
|
||||
border-color: transparent;
|
||||
color: var(--text-subtle);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.build-bar {
|
||||
top: auto;
|
||||
bottom: calc(var(--space-5) + 3.2rem);
|
||||
right: var(--space-2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user