All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to CalVer.
- Analysis Dashboard: add a link to the Observatory in the details dialog, styled to match the Observatory's theme-aware sky (light) / space (dark) look.
- Analysis Dashboard: the Birthday list now shows the first 10 with a toggle to reveal the rest (and collapse again), instead of a static overflow count.
- Analysis Dashboard: the Community Trends lists (Top Events / Top Tags / Top Materials) now show the first 10 with a toggle to reveal up to 30.
- Analysis Dashboard: Top Tags and Top Materials entries now link to their ProtoPedia tag / material pages (Top Events stays plain text).
- Analysis Dashboard: Community Trends now includes recent-window Top Materials and Top Tags rankings — rolling "Last 1 month" and "Last 1 year" windows (by releaseDate, relative to the analysis time) shown next to the all-time lists, each labelled with an approximate human span (days / months / years).
- Analysis Dashboard: show Newborn before Birthday in both the summary bar and the details dialog.
- Analysis Dashboard: relabel the Community Trends lists with an "(All Time)" suffix and a leading emoji to clarify they are all-time totals.
- Analysis Dashboard: the Community Trends Top Tags and Top Materials lists use a denser multi-column layout.
- Internal refactor (no behavior change): route all analysis through a single
AnalysisRepository, rename the home analysis type toAnalysisOverview, and group Observatory insight builders by page underlib/observatory/<page>/. FetchPrototypesSuccessnow carries a requiredlastFetchedAt: Date(the dataset generation's fetch time). Any code constructing this result must supply it.- Internal refactor (no behavior change): extract the analysis dashboard's
details dialog into its own
AnalysisDetailsDialogContentcomponent.
- Observatory pages no longer land mid-scroll after navigating between them;
each opens at the top. Removed the global
scroll-behavior: smooth, which made the App Router's scroll reset animate instead of jump.
- ProtoPedia Observatory - Hello World: Now shows the team and every maker for each work instead of a single collapsed author (or none):
- The Newborn Stars, The First Penguin, The Early Adopters, Long-Term Evolution, and Labor of Love list the team (when present) and all makers, each maker linked to their ProtoPedia profile by display name (not the raw
displayName@profileIdstring).
- The Newborn Stars, The First Penguin, The Early Adopters, Long-Term Evolution, and Labor of Love list the team (when present) and all makers, each maker linked to their ProtoPedia profile by display name (not the raw
- Analysis Dashboard: The Birthday and Newborn prototype lists now show the team (when present) and all makers, each maker linked to their ProtoPedia profile by display name. Anniversary candidate data now carries
teamNmandusers.
- Update
promidasto 3.0.1 andpromidas-utilsto 3.2.1.promidas3.0.1 fixes user-field splitting so@-containing names are parsed correctly (e.g.nisshi.dev | にっし@nishida24now stays two distinct users).- Delegate username decoding to
promidas-utils'parseUsername.
- The prototype card now shows maker display names (not the raw
displayName@profileIdstrings) and links each maker to their ProtoPedia profile. - The Magnum Opus (The Alchemist's Table) now shows maker display names instead
of the raw
displayName@profileIdstrings, and lists team and makers separately (makers link to their ProtoPedia profile).
- Add two material-over-time charts to The Alchemist's Table:
- The Fire of Prometheus — a rank-flow (bump) chart of how the leading materials trade places over the years, with year/month granularity, a top 10 / 20 / 30 depth control (the chart grows taller with depth), and a slider to set the first & last period.
- The Rising Cauldron — the yearly usage volume of the top materials (absolute counts overlaid on one timeline).
- Backed by new per-period rankings (
yearlyTopMaterials/monthlyTopMaterials) computed alongside the existing material insights.
- Add The Elemental Chronicles to The Alchemist's Table — a per-material
chronicle with two facets, driven by one name search and a Show 3 / 30 toggle:
- The Forgers of this Element (the people) — each material's Pioneer (first user), Top user, and Innovator (first to win an award with it), its distinct-maker count, and "reached N makers in D days" adoption milestones.
- The Nature of the Element (the material) — Pairs with (co-used materials), Used for (genre tags), repeat rate, usage lifespan (first → last use), and "reached N works in D days" propagation milestones.
- Materials link to their ProtoPedia page.
- Add The Circle of Masters to The Alchemist's Table — a round table seating
makers by a fact-based, materials-grounded title, each a podium (ties expand
it) with a per-seat eligibility gate:
- The Polymath (most distinct materials), The Weaver (materials per work), The Purist (the top devotee of each material — crownable on several), and The Vanguard (most materials pioneered).
- The Grand Alchemist — makers who hold two or more seats at once.
- Add a "ProtoPedia Summary" card to the Observatory top page, linking to the
external site (https://protopedia-summary.higedaruma.net/).
ObservatoryCardnow supports external URLs (opened in a new tab with an external-link icon) and richReactNodedescriptions.- New
newspapercard color scheme for a vintage newsprint look.
- Add "The Alchemist's Table" Observatory page (
/observatory/alchemists-table), a materials-themed page. Backed by a dedicatedgetMaterialAnalysis/ material-insights source, kept separate from the base analysis so it does not slow the top page.- The Elements — usage-ranked catalog of materials.
- The Monumental Elements — the most-used materials of all time.
- The Magnum Opus — works using the most materials.
- Less is More? — median views and likes by material count.
- An alchemist's timeline of materials, with sparklines: The Primordial Element, The Rising Vapors, The Newfound Element (monthly), Lost Technology.
- Every material links to its ProtoPedia page (tiles, rankings, and recipe chips); works link to their prototype page.
- Long ranking lists collapse behind a "show all / show less" toggle.
- New
alchemycard and header color schemes; Cinzel Decorative + Science Gothic fonts. Concept spec:docs/observatory/content/the-alchemists-table/the-alchemists-table.md.
- Switch The Sci-Fi Lab card font to Science Gothic.
- Add an opt-in promidas in-memory Repository
(
lib/repositories/promidas-repository.ts) behindUSE_PROMIDAS_REPOSITORY(default off;prototypes.tsis the fallback). When enabled, these prototype reads resolve via the promidas snapshot throughapp/actions/prototypes-gateway.tsinstead ofprototypeMapStore:- name lookup (playlist preview)
- all prototypes (analysis hydrate)
- max prototype id
- Remove the unused
getTsvmethod fromPrototypeRepository(it fetched a non-existent/api/prototypes/tsvroute and had no callers), and delete the entirely unusedin-memory-prototype-repositorytest helper. Sample-data TSV export intools/get-sample-data.tsis unaffected (it uses the SDK'sdownloadPrototypesTsvdirectly).
- Flag-gate the no-store SHOW / by-id fetch (
fetchPrototypesNoStore) onUSE_PROMIDAS_REPOSITORYlike the other reads: the promidas fetcher when enabled, otherwise the legacy SDK-based no-store client. Previously this path was always promidas (it was switched ahead of the flag), so with the flag off (the default) it now uses the SDK client -- the flag is now a single master switch (off = fully legacy, on = fully promidas). (#181) - Build the shareable playlist URL from the running environment's origin instead
of a hardcoded production domain. The URL builders now return an origin-less
path (
buildPlaylistUrl->buildPlaylistPath,buildPlaylistUrlWithPathParams->buildPlaylistPathWithPathParams) and the playlist editor prepends the client'swindow.location.origin, falling back toAPP_URLduring SSR (read hydration-safely viauseSyncExternalStore). - Resolve prototype names in the playlist preview card with SWR (
useSWR) instead of a hand-rolleduseEffectfetch. Names now dedupe/cache by id set, the manual cancellation flag is gone, andisLoadingdistinguishes "still loading" (blank cell) from a genuinely missing name ((unknown)). The name column takesw-fullso its width no longer shifts as names arrive. - Fetch the SHOW / by-id prototype via a promidas-backed non-cached client.
getLatestPrototypeByIdnow calls thefetchPrototypesNoStoreserver action inapp/actions/prototypes-gateway.ts(backed bylib/promidas-no-store-client.ts, which uses promidas'sProtopediaApiCustomClient.fetchPrototypesfor fetch + normalize + structured errors). Implemented as a separate path: the SDK-based no-store client, the shared list (force-cache) path, and the random/map-store path are untouched, so it can be rolled back by switching the consumer import. The SDK-basedfetchPrototypesViaNoStoreClientaction andprotopediaNoStoreClientare now@deprecated(retained as a fallback) in favour of the promidas versions. The promidas client logs through the app's pino server logger (via a small adapter) so its diagnostics are consistent with the rest of the server code rather than going to a standalone console logger. (#136, #138) - Adopt the
promidaspackage for ProtoPedia prototype normalization (addedpromidasandpromidas-utilsas dependencies).normalizePrototypeForMppdelegates to promidas'snormalizePrototype, replacing the local normalization utilities (splitPipeSeparatedString,lib/utils/time.ts, now removed), so promidas is the single source of truth for parsing. This adopts promidas's behavior: empty segments are dropped from pipe-separated fields (tags/users/awards/events/materials), missingreleaseDate/updateDatebecomeundefined(not''; the API can return null), and missingsummary/systemDescription/releaseFlg/licenseTypedefault to''/''/2/1. Adds characterization tests. (#136, #137) - Align the internal
PrototypeForMpptype with promidas'sNormalizedPrototypeand make it a direct alias: fullyreadonly, code-typedstatus/releaseFlg/licenseType/thanksFlg, and matching optionality. Consumers are unchanged. (#136, #137) - Upgrade
protopedia-api-v2-clientfrom 2.0.0 to 3.0.0. The v3 type definitions make 9ResultOfListPrototypesApiResponsefields optional (teamNm,users,freeComment,releaseDate,thanksFlg,uuid,revision,releaseFlg,licenseType). Prerequisite for adopting thepromidas/promidas-utilspackages. (#136) - Rename the local
NormalizedPrototypetype toPrototypeForMppto make its app-specific scope explicit and avoid a name clash with theNormalizedPrototypetype exported bypromidas. (#136) - Make
AnalysisDashboardpresentational and stop passing a hook as a prop (useLatestAnalysisHook), which violated the Rules of Hooks and blocked React Compiler memoization of the dashboard and its parents. A newAnalysisDashboardContainernow owns theuseLatestAnalysishook. Also hoist a value block out of atry/catchin the home page so the compiler can optimize the max-prototype-id resolver. (#157, #166) - Extract header-height measurement (the
ResizeObserverand--header-offsetCSS variable sync) out ofMugenProtoPediainto auseHeaderHeighthook, as the first step of incrementally decomposing the oversized home page component. The hook also guardsResizeObserverwith a windowresizefallback and removes--header-offseton unmount. (#168) - Extract the max-prototype-id resolver (the
getMaxPrototypeIdserver action call, its fallback and validation) out ofMugenProtoPediainto auseMaxPrototypeIdhook. Step B of decomposing the home page. (#168) - Use the client logger (
@/lib/logger.client) instead ofconsole.*in the home page so all of its logging goes through one structured, level-aware path (it already usedloggerfor most messages). Warnings and errors still surface at the default client log level. - Extract the command window / cheat-code key sequences (visibility, the
key buffer, the matched-command display, and the Escape-to-close and
special-sequence handling) out of
MugenProtoPediainto auseCommandWindowhook. Step C of decomposing the home page; behavior unchanged. The match-resetsetTimeoutis now tracked in a ref, cleared before re-scheduling, and cancelled on unmount (no stacked timers or post-unmount state updates). (#168) - Extract the prototype-fetching handlers (random, by-id SHOW, and the
playlist-mode fetch) out of
MugenProtoPediainto ausePrototypeFetchinghook. The slot operations stay owned by the page and are injected; the playlist fetch is returned for the upcoming playlist-playback work (#158). Step D of decomposing the home page; behavior unchanged. (#168) - Extract the playlist playback orchestration (the queue/signature/timeout
refs and the queue-prep + timer-loop effects) out of
MugenProtoPediainto ausePlaylistPlaybackhook, and model its state as auseReducerstate machine inusePlaylistPlaybackState(collapsingisPlaylistPlaying/isPlaylistCompleted/processedCountand the random title-card style). The reducer-backeddispatchis stable and position-independent, so the late-running orchestration drives state that is declared early and fed back intousePrototypeFetchingwithout a circular dependency. This removes the blockeslint-disable react-hooks/set-state-in-effectthe imperative setters required. Final step (E) of decomposing the home page; behavior unchanged. (#168, #158) - Split
MugenProtoPediainto a container that wires the hooks and derived state and a presentationalMugenProtoPediaViewthat receives that state and the callbacks as props, so the home view is hooks-free and renderable in isolation. The analysis dashboard is injected as aReactNodeprop (mirroringHeader) to keep the data-fetchingAnalysisDashboardContainerout of the view. Adds Storybook stories for the view. Behavior unchanged. (#71) - Move the home route's top-level
<main>layout shell out of the view into aHomeLayoutcomponent owned byapp/page.tsx, so the page owns the layout shell (mirroringPlaylistEditPage) while feature logic stays inMugenProtoPedia. The view now returns a fragment; the shell renders during Suspense instead ofnull. Behavior unchanged. (#71) - Make the date-based Christmas theme injectable:
resolveThemeByDateandresolveMppThemeTypenow accept an optionalnow: Date(defaulting to the current time), andPlayModeThemeaccepts an optionalnowprop. This makes the theme deterministic for tests (no global clock mocking) and previewable for any date in Storybook. AddsPlayModeThemetheme-confirmation stories (including a date-injected Christmas preview). Behavior unchanged. (#71)
- Enable
typedRoutesso internalLinkhrefs are statically type checked. (#162)
- Upgrade Next.js from 15.5.7 to 16.2.9. (#161)
- Upgrade to ESLint 10 and migrate to the native flat config. (#156, #159)
- Update
@vercel/analyticsto v2 and bump several major dependencies (jsdom, glob, lucide-react, @chromatic-com/storybook). (#152, #155) - Simplify PWA icon generation using
@vite-pwa/assets-generator. (#143)
- Remove the hand-written
<head>from the root layout. In the App Router it broke the Metadata API insertion point, which could push<title>,<link rel="manifest">,<meta name="description">and Open Graph tags into<body>(causing Chrome to report "No manifest detected"). The FOUC theme bootstrap is kept as a plain inline<script>so it still runs synchronously before first paint. (#165) - Make the PWA
apple-touch-iconfull-bleed. (#144)
- Escape
<in the JSON-LD structured data before injecting it viadangerouslySetInnerHTML, so no value can terminate the<script>tag early. Defensive hardening; current values are static constants and the rendered output is unchanged. (#165)
- Disable text selection on the header and the control panel to prevent labels and emojis from being accidentally selected on click or drag. The prototype ID input remains selectable.
- Remove background color classes from the StatusIndicator component.