perf: staged fitText search, avatar/background cache split, CLI/util concurrency - #75
Merged
Merged
Conversation
No wrapping algorithm can fit more content per line than maxWidth, so a font size whose segments' total width alone already implies more lines than the budget allows is guaranteed not to fit — that can be checked with a single whole-segment measurement, without tokenizing and wrapping the text. This is a pure short-circuit: it never changes which font size the search settles on, only skips full wraps that were always going to fail, so it's safe alongside fitText's existing non-monotonic-search caveat (a smaller size can occasionally need more lines than a larger one, from kinsoku). Matters most for long CJK text at a large setScale(): every character boundary is a fallback break candidate there, so a full wrap tokenizes and measures roughly one segment per character at every candidate size.
…vatarCache
loadBackgroundImage delegated straight to loadAvatar, which always went
through the shared avatarCache singleton — the exact coupling avatarCache's
own doc comment says to avoid ("a burst of avatar fetches evicting emoji ...
would be a strange coupling between two unrelated kinds of asset"), just
between avatars and background images instead of avatars and emoji.
With a 64-entry, 5-minute-TTL avatar cache, a burst of distinct avatars could
evict a fixed background image and force a re-fetch on the next render.
loadAvatar now takes an injectable cache (defaulting to avatarCache), and
loadBackgroundImage passes a new, separate backgroundImageCache sized for a
small set of reused assets (16 entries, 30-minute TTL) instead.
Exposes configureBackgroundImageCache/clearBackgroundImageCache/
backgroundImageCacheInfo from the package root, matching the equivalent
avatarCache trio already there.
outdatedCommand and updateCommand awaited checkPackageUpdate, twemojiInfo(+ latestTwemojiVersion) and listInstalledFonts(+checkFontUpdates) one after another, even though the three checks don't depend on each other — only twemojiLatest depends on twemoji's own result. Each is a network or disk round-trip, so the command's total latency was the sum of all three instead of the slowest one. updateCommand keeps its actual updates (and their printed output) sequential and in the same package → Twemoji → fonts order as before; only the read-only status gathering above them now runs concurrently.
resolveCacheDir() calls findProjectRoot() with no override on every font and Twemoji cache-dir resolution that isn't already short-circuited by MIQ_FONT_CACHE_DIR, each one synchronously walking up from cwd checking for package.json. A single run resolves the same startDir every time and a project's own root doesn't move while the process is alive, so the walk only needs to happen once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this change?
Four independent performance fixes found during a codebase review, each its own commit:
fitText: skip the full tokenize+wrap for font sizes a cheap width lower bound already rules out (never changes the result — see the code comment for why it's safe alongside the existing non-monotonic-search behavior). Matters most for long CJK text at a largesetScale().backgroundImage: give it its own cache (backgroundImageCache) instead of sharingavatarCache— a burst of distinct avatars could previously evict a fixed background image, forcing a re-fetch.outdated/update: run the independent package/Twemoji/font checks concurrently instead of one round-trip at a time.update's actual mutations and their printed output stay sequential and in the same order.findProjectRoot: memoize bystartDir— every font/Twemoji cache-dir resolution in a run was re-walking the same ancestor chain.Checklist
npm run cipasses (Biome lint and format)npm run typecheckpassesnpm run testpassesnpm run build && npm run check:buildpasses