Skip to content
Contents

Changelog: packages

What changed in each @haruhimemoe package (ui, next-kit, osu, mirror, pool and the rest), release by release, newest first.

  1. brand0.8.0

    1 changed

    Changed

    • packs gets its own hue, 30 (orange), instead of sharing haruhime's 333.

    brand 0.8.0 in the brand changelog

  2. crowdfund0.1.0

    12 added

    Added

    • fundingGoalSchema, goalTierSchema, donationSchema, refundSchema, settledSchema, currencyCodeSchema, minorUnitsSchema, positiveMinorUnitsSchema: zod schemas with no refinements, so they .extend().
    • currencyExponent, toMinorUnits, netAmount, isRefunded, refundDonation: integer money in any currency, full and partial refunds.
    • totalsByCurrency, totalsFor: gross, refunded and net totals, with settled values and a host-supplied convert.
    • goalProgress, unlockedTiers, nextTier: progress toward a goal and its stretch tiers.
    • topDonors, publicDonation: donor leaderboards and public rows that never reveal an anonymous donor.
    • donationKey, dedupeDonations, hasDonation: dedupe by (source, externalId).
    • @haruhimemoe/crowdfund/kofi: kofiPayloadSchema, parseKofiBody, verifyKofiToken, kofiToDonation, extractKofiTransactionId, KOFI_TYPES.
    • @haruhimemoe/crowdfund/stripe: verifyStripeSignature, parseStripeEvent, stripeEventSchema, stripeToDonation, stripeRefund.
    • @haruhimemoe/crowdfund/paypal: paypalVerifyBody, parsePaypalEvent, paypalEventSchema, paypalToDonation, paypalRefund.
    • @haruhimemoe/crowdfund/github-sponsors: verifyGithubSignature, parseGithubSponsorshipEvent, githubSponsorshipEventSchema, githubSponsorshipToDonation.
    • @haruhimemoe/crowdfund/buymeacoffee: verifyBuyMeACoffeeSignature, parseBuyMeACoffeeEvent, buyMeACoffeeEventSchema, buyMeACoffeeToDonation, buyMeACoffeeRefund.
    • CrowdfundError with a code.

    crowdfund 0.1.0 in the crowdfund changelog

  3. harumin-config0.1.0

    1 added

    Added

    • Guild settings (guildSettingsSchema, defaultGuildSettings, readGuildSettings, guildSettingsPatchSchema, applyGuildSettingsPatch), auto-embed keys, defaults and labels, /track entries, collection names, the bot's service routes and their bodies, and guildIconUrl.

    harumin-config 0.1.0 in the harumin-config changelog

  4. invites0.1.0

    6 added

    Added

    • zod schemas for invites, drafts, terms, deadlines, reminders, history, reminder prefs and blocks, all .extend()-able. INVITE_KINDS, INVITE_STATES, HISTORY_ACTIONS.
    • createInvite, transition, allowedActions, isOpen, isExpired, effectiveState: the state machine over pending, countered, accepted, declined, withdrawn, cancelled and expired.
    • checkTerms, diffTerms.
    • checkBlocks, checkCooldown, checkRateLimit, canSend: send rules.
    • canRemind: reminders for accepted invites per the agreed plan and the recipient's prefs.
    • inbox: needs my answer, waiting on them, active, past.

    invites 0.1.0 in the invites changelog

  5. mirror0.1.0

    7 added

    Added

    • createMirrorClient with downloadSet (failover across hinai, osu.direct, Mino and Sayobot, with zip checks, progress and abort), downloadLinks, health, and hinai's getBeatmaps and getAvailability.
    • @haruhimemoe/mirror/hinai: the whole @haruhimemoe/hinai 0.3.1 client, moved here unchanged. @haruhimemoe/hinai is deprecated in its favor; swap the import path.
    • @haruhimemoe/mirror/testing: @haruhimemoe/hinai/testing, moved here unchanged (msw 2 as an optional peer).
    • HinaiError, HinaiErrorCode, OSZ_MIME, parseRetryAfter, backoffDelayMs and MAX_RETRY_DELAY_MS from the root too, the same objects as /hinai's.
    • Per-mirror cooldowns: a network error, timeout or 429 at once, other failures after MIRROR_FAILURE_LIMIT in a row.
    • MirrorError with not_found, unavailable and download_disabled, and every failed attempt.
    • MIRRORS, DEFAULT_MIRRORS and custom Mirror objects, with missingStatuses for mirrors that don't answer 404.

    mirror 0.1.0 in the mirror changelog

  6. next-kit0.15.0

    3 added, 1 changed

    Added

    • i18n (new subpath, no next-intl import): LocaleConfig, DEFAULT_LOCALES, hasLocale and negotiateLocale (saved user locale, then Accept-Language q-order with a region fallback, then the default). The header is capped at 1 KB and 20 entries before parsing.
    • i18n/next-intl (new subpath): createRequestConfig (injected app and package catalog loaders, merged deeply with the app's last) and createI18nMiddleware (next-intl's middleware, "as-needed" prefix, NEXT_LOCALE cookie on an optional parent domain, then the app's own step). Plus mergeMessages and resolveRequestConfig. next-intl ^4.14.0 is an optional peer dependency; only this subpath needs it.
    • seo: hreflangAlternates(config, path, baseUrl).

    Changed

    • seo: pageMetadata takes alternates: { languages } and writes it next to the canonical.

    next-kit 0.15.0 in the next-kit changelog

  7. next-kit0.14.0

    2 added, 1 changed

    Added

    • account (new subpath): account export and delete across apps. createAccountHandlers gives an app POST handlers for /api/internal/account/{export,delete} behind its own bearer secret (ACCOUNT_FANOUT_SECRET, at least 32 bytes), with a zod-checked { userId } body and no-store answers. fanOut lets the hub call every registered app with that app's secret: https only (http on localhost), redirects never followed, a 10 s timeout, three tries for delete, and unset secrets reported as not_configured. exportBundle packs the results into one JSON download. Plus secretFor, appUrl, usableSecret and the AccountApp registry type.
    • inbox (new subpath): invites and notifications in the identity database. createInboxStore (invites upserted by id and held by one app, notifications with an unread filter, markRead, a 90-day TTL, deleteFor for account delete), createInboxRoutes for the hub's /api/internal/inbox (the matching secret picks the app, checked against every app with no early exit; another app's invite id is a 409), createInboxClient for apps, matchApp and inboxIndexSpecs.

    Changed

    • mongo: buildIdentityIndexes also builds the inbox indexes.

    next-kit 0.14.0 in the next-kit changelog

  8. next-kit0.13.0

    3 added

    Added

    • auth: Discord link for the hub. discordLinkConfig(env, hubUrl) (null when DISCORD_CLIENT_ID or DISCORD_CLIENT_SECRET is unset, which turns the feature off), createDiscordLinkRoutes (POST start behind refuseCrossSite, GET callback, POST unlink; a signed, 10-minute, host-only state cookie; writes only discordId and discordUsername, never a token or an account row; ?discord=linked|taken|error), findUserByDiscordId (refuses banned users), DISCORD_SCOPES and DISCORD_STATE_COOKIE.
    • mongo: buildIdentityIndexes adds a partial unique index on user.discordId (IDENTITY_INDEXES.userDiscordId). A duplicate-key error on link maps to taken.
    • api-keys: scopes. createApiKeyStore({ scopes }) declares the app's names, issue(userId, scopes?) defaults to ["*"] and refuses undeclared names, ApiKeyInfo and ApiKeyMatch carry scopes (a stored key without the field reads as ["*"], so nothing migrates), and withApiKey(handler, { scope }) answers 403 insufficient_scope. New hasScope, normalizeScopes, ALL_SCOPES and API_INSUFFICIENT_SCOPE.

    next-kit 0.13.0 in the next-kit changelog

  9. next-kit0.12.2

    1 fixed, 1 changed

    Fixed

    • check's next-kit migrate-identity now skips users without a numeric osuId (system accounts) entirely: they're never grouped with other users, never inserted into identity, and never get an idMap entry, so references pointing at them are left untouched instead of being rewritten to nowhere. MigrateReport gains usersSkipped; the CLI's summary line reports it alongside usersSeen/usersWritten.

    Changed

    • auth's old SessionReader type (the better-auth instance shape used by getOsuUser/getSessionUser/requireSession/requireAdmin) is renamed to OsuAuthInstance, so SessionReader can mean one thing: createSessionReader's own instance type (session-reader.ts), which the barrel used to export only as SessionReaderInstance to dodge this exact collision. session.ts keeps a SessionReader alias of OsuAuthInstance for source compatibility, and session-reader.ts keeps a SessionReaderInstance alias of SessionReader (the barrel exports both — bb/packs/pools/haruhime.moe all import SessionReaderInstance, none import the old SessionReader, so this is a non-breaking patch for every real consumer).

    next-kit 0.12.2 in the next-kit changelog

  10. next-kit0.12.1

    1 changed

    Changed

    • check's next-kit migrate-identity is idempotent now that the hub is live and identity holds real users who signed in there directly: a source group's osuId matching an existing identity user merges into it (that user's _id and fields win outright, only its own missing fields get filled from the source) instead of refusing on a non-empty identity. Accounts dedupe by providerId+accountId and API keys by hash against identity's own existing rows too, so a second --execute run inserts nothing new. MigrateReport gains usersMerged. --drop-old is unchanged: still its own run, still refusing while identity is empty.

    next-kit 0.12.1 in the next-kit changelog

  11. next-kit0.12.0

    1 added, 2 changed

    Added

    • Identity core (breaking for every app — see below): mongo's createMongo gains identityDbName/getIdentityDb() so the hub reads an identity database on the same client, buildIdentityIndexes builds its four indexes, auth's createOsuAuth gains cookieDomain/trustedOrigins for the hub (every better-auth cookie, including OAuth state and PKCE, on the parent domain) and a 30-day session with a 1-day updateAge, createSessionReader reads a satellite's session with zero database writes (raw cookie verification, no betterAuth() instance) and pings the hub to refresh a session past updateAge, getSessionUser/requireSession/requireAdmin read either source and refuse a banned user, server's safeAbsoluteNext is the hub's exact-hostname allowlist for a satellite's absolute next (it returns the normalized URL), hubSignInUrl builds a satellite's link to the hub's sign-in page through the same allowlist, and check's next-kit migrate-identity merges bb/packs/pools users into identity by osuId (dry run by default).

    Changed

    • Breaking. mongo's onConnect now takes one { db, identityDb?, client } argument instead of (db, client).
    • Breaking. The identity user gains locale, notificationPrefs, bannedAt, banReason, limits, discordId and discordUsername (auth's IDENTITY_USER_FIELDS, merged into every createOsuAuth instance's additionalFields, single-DB apps included). OsuSessionUser and OsuSession gain bannedAt. packs' system field does not move with this release: it stays a plain field on packs' own user collection for now. Once packs cuts over to the shared identity database (after the migration script runs), system must move to an app-side profile collection keyed by userId, because the identity user row is shared with bb and pools and has no room for one app's own flag. Do that move in the same deploy that switches packs to createSessionReader, not before.

    next-kit 0.12.0 in the next-kit changelog

  12. next-kit0.11.1

    1 fixed
  13. osu0.6.0

    4 added

    Added

    • Profiles and scores on the client: getUserProfile(user, { beforeCall, ruleset }) (an OsuUserProfile with one ruleset's statistics), getUserScores(userId, "best" | "recent" | "firsts", { ruleset, limit, offset, includeFails }), getBeatmapUserScores(beatmapId, userId, { ruleset }), getBeatmapScores(beatmapId, { ruleset, limit }), and getBeatmap(beatmapId) (metadata plus maxCombo and status). Score calls send x-api-version: 20220705 for the lazer score format; a row that fails the schema is left out. For harumin.
    • Score shapes in /shapes: osuScoreSchema, toOsuScore, osuModSchema, SCORE_RANKS, and the types OsuScore, OsuScoreRow, OsuScoreBeatmap, OsuScoreBeatmapset, OsuScoreUser, OsuMod, ScoreRank. Profile shapes: osuUserProfileSchema, toOsuUserProfile, OsuUserProfile, OsuUserStatistics.
    • formatMods and formatAccuracy in /format.
    • OSU_SCORES_API_VERSION, OSU_SCORES_LIMIT, and the types UserScoreType, UserScoresOptions, BeatmapScoresOptions, BeatmapDetail. New public exports, so this release is a minor version.

    osu 0.6.0 in the osu changelog

  14. osu0.5.0

    5 added

    Added

    • getMatch(match, { beforeCall, maxPages }) on the client: a multiplayer match from /api/v2/matches/{id} by id or mp link, as { match, complete }, or null on 404. It pages back through the events with before until the first one (at most maxPages, 50 by default), merging events and users from every page. It rejects with code "budget" when beforeCall refuses the first page; a refused later page ends the call with complete: false.
    • Match shapes in /shapes: osuMatchResponseSchema, osuMatchEventSchema, osuMatchGameSchema, osuMatchScoreSchema, toOsuMatch, toMatchEvent, parseMatchId, MATCH_SCORING_TYPES, MATCH_TEAM_TYPES, and the types OsuMatch, OsuMatchResponse, MatchEvent, MatchGame, MatchScore, MatchTeam.
    • @haruhimemoe/osu/match, safe in browsers: matchGames, gameStatus, listGames (with warmups), isTeamGame, gameWinner (team vs or head-to-head, by the room's win condition or by score, accuracy or combo), mapWins, and matchCosts with MATCH_COST_FORMULAS: Bathbot's, osu!plus's, Flashlight's (as osu!plus ships it) and Elitebotix's mixed formula, picked by name.
    • @haruhimemoe/osu/tournament, safe in browsers: buildLazerBracket writes the osu!lazer tournament client's bracket.json from teams, rounds and matches, and LAZER_BRACKET_FILENAME.
    • OSU_MATCH_EVENTS_LIMIT and OSU_MATCH_PAGE_LIMIT, and the types MatchOptions and MatchLookup. The root entry point re-exports /match and /tournament. These are new public exports, so this release is a minor version.

    osu 0.5.0 in the osu changelog

  15. time0.1.0

    6 added

    Added

    • @haruhimemoe/time/zone: isTimeZone, assertTimeZone, listTimeZones, offsetMinutes, formatOffset, parseOffset, zoneOffset, offsetZone, toWallTime, fromWallTime (Temporal's gap and overlap rules), weeklyWindow and WEEKDAYS.
    • @haruhimemoe/time/availability: weekly grids in UTC slots with fromZone, toZone, localSlot, createAvailability, unionAvailability, intersectAvailability, countAvailability, resampleAvailability, isFreeAt, isFreeDuring, slotsPerWeek, weekSlotOf and availabilitySchema. Grids made by fromZone follow their owner's clock across DST.
    • @haruhimemoe/time/slots: findSlots (ranked match starts with who is free, staff, need, filter, tiebreak, run merging), compareSlots and MAX_STARTS.
    • @haruhimemoe/time/region: Region, regionSchema, defineRegions, CONTINENTS, CONTINENT_OF_COUNTRY, regionOfCountry and checkRegionLock.
    • @haruhimemoe/time/ics: buildCalendar, escapeText and foldLine (RFC 5545).
    • TimeError with a code for every refusal.

    time 0.1.0 in the time changelog

  16. tourney0.2.0

    6 added, 2 changed

    Added

    • Pick/ban phase order: rules.phases (PickBanPhaseSchema) for split bans and any other order, pickBanPhases, and phase in the replay state. The 0.1 protects / bans rules still work as shorthand and give the same results and refusals.
    • rollFirst: two rolls and the roll winner's choice to the first turns; a tie is refused.
    • Third place match in single elimination (thirdPlace: true, round 3RD, before F).
    • placements: standings from a bracket in both formats, filled in as rounds finish.
    • @haruhimemoe/tourney/mp: fromOsuMatch reads an osu! multiplayer match (from @haruhimemoe/osu) into maps, score, winner and a problem list. MapResultSchema takes an optional gameId.
    • Groups: roundRobin, groupStandings with points and tiebreaks (StandingsRulesSchema), swissPairings and swissRounds, snakeGroups, seedsFromGroups, GroupSchema.

    Changed

    • @haruhimemoe/osu (0.5 or newer) is an optional peer dependency, needed only for @haruhimemoe/tourney/mp.
    • RoundSchema.side can be "third". Code that switches over every side needs a case for it.

    tourney 0.2.0 in the tourney changelog

  17. tourney0.1.0

    9 added

    Added

    • Round ladders for single and double elimination (buildLadder): RO/QF/SF/F, losers rounds, grand final with optional reset, qualifiers, best-of per round.
    • Brackets with progression (createBracket, reportResult, reportForfeit, clearResult, champion): standard seeding, byes, losers bracket drops, grand final reset.
    • Qualifier ranking by total score or average rank (rankQualifiers), and standard seed order (seedPositions).
    • Stored match records (MatchSchema) with maps, per-player scores, lineups, staff, links and reschedules; score helpers.
    • Pick/ban replay against a @haruhimemoe/pool pool (checkPickBans): protects, bans, picks, tiebreaker, whose turn is next.
    • Teams with rosters, subs and per-map lineups; solo sides.
    • Captain drafts: snake or linear order, the clock, picks, deadlines.
    • Tournaments with phases and a registration window; registrations with review, role conflicts and caps.
    • Scheduling through @haruhimemoe/time (suggestMatchTimes) and reschedule rules (checkReschedule).

    tourney 0.1.0 in the tourney changelog

  18. ui0.20.0

    3 added

    Added

    • @haruhimemoe/ui/theme-light.css, a light scheme: import it after theme.css. Light surfaces, tinted ink, h1 as the dark accent and h2 as the pale one, dark code tokens, and a negative contrast lift. Every ink step keeps 4.5:1 on every surface at every hue; the README gives the --h1-l for hues 30 to 200. For harumin.haruhime.moe's white site.
    • Lightness variables for every background and content step: --b1-l to --b6-l and --c1-l (--c2-l to --c4-l already existed). Unset, the dark palette is unchanged.
    • --wordmark-ink: HaruhimeWordmark's white parts read it (default #ffffff), so the light scheme can ink them.

    ui 0.20.0 in the ui changelog

  19. ui0.19.0

    3 fixed, 5 changed

    Fixed

    • CodeBlock (and so MdxPre's fenced blocks) no longer shows a blank line between every line. Each line was a display:block span with a "\n" text node between them, and inside the <pre> that text node rendered as an extra empty line. The text node is gone; the copy button still copies the source text with its real newlines. The <code> element's textContent no longer contains newlines.
    • ContentPage's header has a bottom margin (mb-8), so the meta row and "Copy as Markdown" button no longer touch the body.
    • SiteHeader's actions slot is a centered flex row (flex items-center gap-3), so CommandPaletteButton and an account link like "Sign in" share a center line instead of sitting about 4px apart on their text baselines.

    Changed

    • CopyMarkdownButton is a compact 28px pill (h-7 px-3, still 44px on a coarse pointer) to sit level with the byline's small text. Button size="sm" is a 28px icon square, too narrow for a label, so this is the secondary button with a smaller height and padding.
    • SiteHeader under sm, when it has actions: the nav moves to its own full-width row below, so the actions stay on the brand's row instead of wrapping under the nav. The DOM order (brand, nav, actions) and so the tab order is unchanged.
    • CommandPaletteButton hides its Ctrl K/⌘K hint under sm, leaving the magnifier (and any children).
    • ContentPage with a toc: the toc/body grid gap is gap-6, xl:gap-10 (was gap-8 at every width), matching ContentLayout's nav/body gap (gap-6, lg:gap-10). On phones the "On this page" disclosure now sits the same 24px above the body as the "Contents" disclosure.
    • README and SECURITY.md: the Discord invite is now https://haruhime.moe/discord and the security contact email is haruhime@haruhime.moe.

    ui 0.19.0 in the ui changelog

  1. next-kit0.11.0

    1 added

    Added

    • legalMarkdownTransform(site) in legal: a mdxToMarkdown transforms entry that turns each self-closing legal block tag (<LegalContact />, <DataWeKeep />, <Processors />, <YourRights />, <DmcaNotice />, <NoWarranty />, <Changes /> and <Changes date="YYYY-MM-DD" />) into Markdown carrying the same words as its React block. Without it, an app that drops the legal blocks straight into its content/legal/*.mdx loses that text from its .md mirrors and llms-full.txt, since mdxToMarkdown strips unknown capitalized JSX. The seven blocks' sentences and list items now live in a new legal/copy.ts, shared by blocks.tsx and legalMarkdownTransform so the two outputs can't drift apart; blocks.tsx's rendered output is unchanged.

    next-kit 0.11.0 in the next-kit changelog

  2. next-kit0.10.0

    1 changed

    Changed

    • Breaking. next-kit check's legal standard requires all five pages of the legal convention: content/legal/{terms,privacy,your-privacy-rights,copyright,disclaimers}.mdx (was terms and privacy only).

    next-kit 0.10.0 in the next-kit changelog

  3. next-kit0.9.0

    1 added, 1 changed

    Added

    • legal entry point: the five-page legal convention every service ships (terms, privacy, your-privacy-rights, copyright, disclaimers). A LegalSite config (site name, operator, contact email, effective date, data stores, processors, cookies), seven plain server-safe blocks an app drops into its own legal MDX (LegalContact, DataWeKeep, Processors, YourRights, DmcaNotice, NoWarranty, Changes; no hooks, no @haruhimemoe/ui), and legalEntries(site, pages?) for the five standard ContentEntry records so apps stop hand-writing the same titles and descriptions. next-kit's first .tsx entry point. LegalSite.hosting adds an optional line to DmcaNotice; Changes takes an optional per-page date.

    Changed

    • The @haruhimemoe/ui peer range also allows ^0.18.0.

    next-kit 0.9.0 in the next-kit changelog

  4. next-kit0.8.0

    3 changed

    Changed

    • Breaking. The @haruhimemoe/ui peer range is now ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 (0.15 to 0.17 not yet published): DeleteAccountForm needs ui's ConfirmDialog.
    • DeleteAccountForm is a "Delete my account" button that opens a dialog: what goes is its description, the username is typed there, and a refusal or no answer is said in the dialog, which stays open. Once the account is deleted, focus moves to the "Your account is deleted." line.
    • The account components use ui's text tones (Text, textClasses) and Button's own width. No change in how they look.

    next-kit 0.8.0 in the next-kit changelog

  5. next-kit0.7.0

    1 added

    Added

    • vcs entry point: createRevisionStore, document history in MongoDB on top of @haruhimemoe/vcs (a new optional peer). Saves name their base revision and merge onto anything newer; conflicts write nothing and come back for the client to resolve. Also revert, diffs, author renames, history removal and autosave pruning.

    next-kit 0.7.0 in the next-kit changelog

  6. pool0.3.0

    1 added

    Added

    • reorderSlot(pool, mod, beatmapId, to): move a map within its own bucket (or the no-slot group) without changing its mod, renumbering the group 1..N. packs.haruhime.moe's map drag and drop uses this.

    pool 0.3.0 in the pool changelog

  7. ui0.18.0

    2 added, 1 changed

    Added

    • Button new size="sm": a 28px icon-only square (still over the 24px WCAG 2.5.8 target); md and lg unchanged.
    • ChevronUpIcon and ChevronDownIcon: plain chevrons as static inline SVGs, matching DiscordIcon/GitHubIcon's shape (default size-5, aria-hidden, a className replaces the size).

    Changed

    • SortableMoveButtons default content is now the two chevron icons (aria-hidden; the accessible name stays on the button), sized "sm"; upText/downText still swap in words and size back to "md" by default. New size prop overrides either default. New orientation prop ("horizontal" default, "vertical" stacks the buttons in a column). Each button's title now mirrors its aria-label. Accessible names ("Move {label} up"/"down") are unchanged.

    ui 0.18.0 in the ui changelog

  8. ui0.17.0

    5 added, 6 changed

    Added

    • @haruhimemoe/ui/remark: remarkFigures (a paragraph holding only an image becomes a <figure>, its title moving into a trailing <figcaption>), remarkEmbeds (a paragraph holding only a bare YouTube/Twitch URL becomes a click-to-load player), parseEmbedUrl and type EmbedTarget (every YouTube URL form with start times, Twitch videos, clips and channels). articleData, type ArticleData and type TocItem: a flat table of contents (h2 to h4), a word count (Intl.Segmenter, Japanese-safe) and a reading time from a document's heading ids. remarkHaruhime options figures, embeds (default true), wordsPerMinute (default 200), mdxExports (default false: adds export const toc/readingMinutes/words to a compiled MDX module, an author's own export of the same name kept) and collect (hands the same data to a function, for react-markdown, whose plugin options don't have to cross Turbopack's serializable boundary). haruhimeSanitizeSchema and type SanitizeSchema: extends an hast-util-sanitize schema (usually rehype-sanitize's defaultSchema) with the tags and attributes this kit's plugins write; trusted: true drops the user-content- id prefix for an app's own READMEs and changelogs. rehypeLocalHrefs and type HastLike: rewrites an in-page #x link to #user-content-x when the sanitizer prefixed the id but not the href. mdxMarkdownTransforms (plus figureMarkdown, embedMarkdown, linkCardMarkdown): turns <Figure>, <Embed> and <MdxLinkCard> into their Markdown line for an app's .md mirror, skipping fenced code. remarkHeadingIds now ids h2 through h4 (was h2/h3).
    • @haruhimemoe/ui/mdx new element overrides, through mdxComponents: h4 (same anchor treatment as h2/h3), img (a plain, lazy, async-decoded <img>), input (a GFM task-list checkbox becomes a named, disabled checkbox), details (a native <details>/<summary> pair styled like Disclosure's toggle, works with no JS), kbd (Kbd's look) and div (a remarkEmbeds data-embed div renders Embed). The GFM footnote label h2 now renders bare, sr-only, with no anchor.
    • @haruhimemoe/ui/mdx new article components: Figure (a sized MdxImg with an optional caption, credit and eager/priority loading), Steps (a numbered-rail wrapper for an ordered list), Embed (a click-to-load YouTube/Twitch player in a 16:9 box, with a poster image and a plain-link fallback for an unrecognized URL; poster={false} or a local poster replaces YouTube's own thumbnail), MdxLinkCard (one link named by its title, a description and a source line), Schedule (an ordered list of when/label/note rows, when a <time dateTime> or plain text), Glossary/Term (a <dl> of entries anchored at #term-<slug>, with alias anchors, and a dotted-underline link to one), Kbd/kbdClasses (re-exported from the root), and type MdxArticleModule (the structural shape of a compiled MDX module with mdxExports: true, for await import(...)), type ArticleData and type TocItem (re-exported from ./remark).
    • Root @haruhimemoe/ui: Toc, type TocProps, type TocItem: an article's table of contents, a sticky column from xl up plus a phone disclosure, both labelled "On this page", nesting a flat TOC by heading depth. Kbd, type KbdProps, kbdClasses: a keyboard key, shared with the command palette's own shortcut hints. ContentPage grows authors (type ContentAuthor, through the new ContentByline), published, lastUpdated now formatted "Oct 4, 2026" (see Changed), readingMinutes, toc (rendered beside the body from xl up) and footer (rendered after the body, outside Prose), plus proseSize passed straight to Prose.
    • Prose new size prop ("base" | "sm", same as ContentPage's proseSize) and new element styles: h4, dl, figures, task lists, footnotes and details.

    Changed

    • h4 headings in existing Markdown and MDX now get an id and a hover-revealed # anchor, through remarkHeadingIds' new depth-4 support.
    • A paragraph holding only an image now renders as a <figure>; one holding only a bare YouTube/Twitch URL now renders a click-to-load player. Turn either off per document with { figures: false } / { embeds: false } passed to remarkHaruhime.
    • Prose now styles h4, dl, figures, task lists, footnotes and details wherever they appear in its content.
    • mdxComponents has six more keys (h4, img, input, details, kbd, div). An app's own key of the same name still wins.
    • The GFM footnotes label h2 loses its # anchor (it was a visible anchor beside invisible text).
    • ContentPage prints ISO dates as "Oct 4, 2026" instead of "2026-10-04" on every docs, guides and legal page. ContentNav's classes move to contentNavStyles.ts, shared with Toc, with no visual change.

    ui 0.17.0 in the ui changelog

  9. ui0.16.0

    2 added

    Added

    • MapCard, MapSetCard, MapGroup, MapCover, MapPreviewButton and MapCopyScope: map display from plain props. MapCard draws one map as a row (packs' slot row) or a card (osu-web's beatmapset panel), with no background, the set's cover or a blurred cover behind a b5 overlay, comfortable or compact density, loading/missing/error states, an optional slot pill, status, stars and stats (overridable per key), an optional Copy ID, and leading, preview, badges, details and actions slots. Its map prop takes @haruhimemoe/osu's BeatmapMeta as-is. MapSetCard is a set with its difficulties, MapGroup a bucket with its heading, count, empty slots and one shared Copy ID scope. MapPreviewButton plays a set's preview clip from b.ppy.sh (one clip per page), stopMapPreview() stops it. Also mapCoverUrl, MAP_STATUS_LABELS and the types MapCardProps, MapCardLabels, MapData, MapSetCardProps, MapSetDifficulty, MapGroupProps, MapCoverProps, MapCoverSize, MapPreviewButtonProps.
    • CopyButton statusPosition ("end" by default, "start" puts the status before the button) and reserveStatus (keeps the status's width so a press never moves the button).

    ui 0.16.0 in the ui changelog

  10. ui0.15.0

    1 added

    Added

    • Sortable lists: useSortable, SortableList, SortableHandle, SortableMoveButtons, SortableLayer, moveItem, SORTABLE_ITEM and SORTABLE_CONTAINER, with their types. Drag by mouse, touch or pen (pointer events only, no HTML5 drag and drop) or by keyboard (Space or Enter to pick up, the arrows, Home, End, PageUp and PageDown to move, Space or Enter to drop, Escape to cancel); a click with detail 0 lifts and drops for screen readers in browse mode. Up and Down buttons run on the same path. "between" and "onto" containers, nested containers, canDrop refusals with a reason, async onMove with a pending state, auto-scroll near edges, an assertive live region with overridable text, a pointer chip that shows the label and why a drop won't take, and focus that stays on the moved item's handle or button. The app owns the data. No new dependencies.

    ui 0.15.0 in the ui changelog

  11. ui0.14.0

    4 added, 1 fixed

    Added

    • Dialog (client): the modal base, a native <dialog> that follows a controlled open. It focuses initialFocus, locks the page's scroll (counted, restoring the page's own inline overflow), and hands focus back on close or on unmount to the opener, else to returnFocus(). Escape and a backdrop press call onDismiss instead of closing; a press only counts as a backdrop press when it starts and ends there; a close the browser makes is reported as "browser". A dialog opened from inside one that closes at the same time returns focus to that one's opener. Fades in on the motion tokens (motion), closes at once. New types: DialogProps, DialogDismissReason.
    • ConfirmDialog (client): a confirm in a modal alertdialog on Dialog, opened from its own trigger or a controlled open/onOpenChange. Focus starts on Cancel; Enter submits; one run at a time with pendingLabel, busy buttons that keep focus, and Escape and backdrop ignored while pending; a throw keeps it open with failedMessage in an alert; tone="destructive" uses the danger button; typeToConfirm adds a field (no autocomplete, autocapitalize or spellcheck) and keeps Confirm aria-disabled until the text matches; returnFocus for a confirm that removed its own row. New types: ConfirmDialogProps, ConfirmTone.
    • danger Button variant: white on rose-700, rose-800 on hover. InlineConfirm's confirmVariant and TypeToConfirm's variant take it too.
    • README: "Which confirm", the rule for no confirm, InlineConfirm, ConfirmDialog, ConfirmDialog with typeToConfirm, and TypeToConfirm.

    Fixed

    • CommandPalette is built on Dialog: closing it puts back the page's own inline overflow instead of clearing it, a text selection dragged from the input onto the backdrop no longer closes it, and a command that opens another dialog sends focus back to whatever opened the palette once that dialog closes.

    ui 0.14.0 in the ui changelog

Older releases are on each repo's own changelog page.