Skip to content
codefastlabs

Command Palette

Search for a command to run...

v0.9.0· Changelog

@codefast/theme

0.9.0

Minor Changes

  • #976 engines.node is now >=24.0.0, up from >=22.12.0, and Node 22 is no longer supported. Node 24.0.0 is the first release with explicit resource management built in (using, await using, DisposableStack, AsyncDisposableStack, SuppressedError) and all of ES2025, so the packages use both as the platform ships them instead of shimming them for an older line, and the CI matrix runs the unit suite on 24.0.0 itself. Move to Node 24, or stay on the current minor while a deployment still runs Node 22.

Patch Changes

  • #977 The README states the browser floor: Chrome and Edge 136, Firefox 136, and Safari 18.4 or later, the first releases that ship every ES2025 builtin. @codefast/di's README also says what a browser program without explicit resource management does: it keeps skipLibCheck on and calls dispose() instead of await using.

  • #965 README.md now states TypeScript 7 or later as the floor for the package's types — the one compiler every @codefast/* package is built and checked with. No code or declaration changed.

0.8.4

Patch Changes

  • #892 4c97c5b Thanks @thevuong! - Lower the monorepo's Node floor from 24 to 22.12, so the packages install and run on the active Node 22 LTS line.

    engines.node becomes >=22.12.0 across every package — the floor the shared toolchain (oxlint, Vite, Vitest, TanStack Start) already requires. Development stays on the latest Node (.node-version) for speed, and a CI matrix exercises the floor and the active LTS directly, so the floor is a contract CI proves rather than one everyone has to run. @types/node is pinned to the floor's major (^22), with a workspace override holding the whole tree there so a dev tool's @types/node: "*" peer can no longer pull a newer major and mask an API the floor lacks. The floor stays mechanical, not advisory: @codefast/di keeps its own Map upsert helpers rather than the ES2025 Map.prototype.getOrInsert (which would raise the floor to 26) and its lib stays ES2024. @codefast/cli's mirror step now calls the node:path functions directly instead of aliasing them, which the floor's types correctly flag as unbound methods.

    The shared @codefast/typescript-config presets pin lib and target to ES2024 (was ESNext) so the compiler's ECMAScript surface matches the Node floor: an ES2025 builtin such as Map.prototype.getOrInsert now fails to type-check rather than compiling and crashing on Node 22.12. @codefast/di and @codefast/di-testing already pinned lib and are unchanged.

    Internal subpath imports move from a #/ prefix to a bare # (#core/token, not #/core/token), and the package.json#imports keys become #*/#tests/*/#examples/* to match. Node's native ESM resolver rejects a #/-prefixed specifier with ERR_INVALID_MODULE_SPECIFIER on the whole Node 22 line (and on Node 24 before 24.14), and each package ships those specifiers verbatim inside its published dist/*.js for a consumer's Node to resolve — so this rename is what actually lets the packages import on the new floor. Purely internal: a consumer's own import paths are unchanged.

0.8.3

Patch Changes

  • #850 db17956 Thanks @thevuong! - Import Zod as a namespace (import * as z from "zod") so bundlers can tree-shake it. The named import { z } kept Zod's full locale set in the bundle; the appearance provider's browser bundle now drops roughly 80% (~91 KB → ~18 KB gzip).

0.8.2

Patch Changes

  • #812 f051471 Thanks @thevuong! - Raise the published dependency floors: zod to 4.5.4 (@codefast/cli, @codefast/theme), lucide-react to 1.41.0 and react-hook-form to 7.87.0 (@codefast/ui).

  • #791 37a212b Thanks @thevuong! - Point the README's license badge and "License" section at the package's own LICENSE file instead of the monorepo root.

  • #796 ba04d27 Thanks @thevuong! - Rewrite the README around one structure — what the package is, installation and requirements, a complete quick start, the concepts in learning order, then the documents and the license — with every API claim checked against the current code and no figures that go stale.

  • #784 ad2f93a Thanks @thevuong! - Ship the MIT LICENSE file in the published package. files already listed it, but the file was missing from the package directory, so the tarball had none.

0.8.1

Patch Changes

  • #748 bde6d1b Thanks @thevuong! - Mirror the committed appearance ref in a layout effect so a programmatic setAppearance landing before the passive flush compares against the just-committed appearance instead of the previous one.

0.8.0

Minor Changes

  • #745 5cbb93a Thanks @thevuong! - fix(theme): harden appearance provider/script and tighten public types

    Three correctness fixes plus two type-safety tightenings:

    • Overlapping setAppearance calls with disableTransition enabled could orphan a transition-suppression <style> — enableTransitionsRef was overwritten before the previous cleanup ran — leaving *{transition:none} in <head> forever and permanently disabling CSS transitions. Each call now flushes the pending suppression before injecting a new one.
    • AppearanceScript now runtime-validates its appearance prop (as AppearanceProvider already did), so an invalid value can no longer be written to <html> before first paint or diverge from the provider's fallback.
    • applyColorScheme no-ops during SSR instead of throwing on window access, matching its sibling suppressTransitions.
    • Public props on AppearanceProvider and AppearanceScript now accept explicit undefined (?: T | undefined) under exactOptionalPropertyTypes, so JSX callers can pass possibly-undefined expressions such as nonce and appearance.

    Breaking (type surface only, no runtime behavior change): appearances is now ReadonlyArray<Appearance> (was Array<Appearance>), so assigning it to a mutable Appearance[] is now a compile error.

0.7.0

0.6.2

0.6.1

0.6.0

Patch Changes

  • #690 f4b1aa6 Thanks @thevuong! - Report the failures that were being swallowed or mislabelled, and derive the types the build emits.

    @codefast/di:

    • @inject, @postConstruct and @preDestroy on a static member now throw StaticMemberDecoratorError instead of InternalError. All three act on one instance, so this is caller misuse — and InternalError means the library broke, which sent anyone catching it to file a bug against their own mistake. SPEC §10 already recorded that mistake for predicate ambiguity.
    • AsyncResolutionError names the token the caller asked for and the token whose factory is async, which is what SPEC has always specified. Every throw site passed the same token twice, so the message read "Token 'X' requires async resolution because 'X' in its dependency chain has an async factory"; a resolve(App) that fails on an async Database now says so. asyncSourceToken defaults to tokenName for the case where the requested binding is itself the source.
    • A MetadataReader that names a @postConstruct/@preDestroy method the instance does not have raises InvalidMetadataError instead of skipping the hook — a hook that silently never runs is the failure a caller cannot see. InvalidMetadataError's message no longer says "constructor", since it now covers both answers; the specifics moved into reason.
    • MissingScopeContextError from ScopeManager names its token instead of "(unknown)", and the scoped read takes one map lookup where it took two.
    • Token, Constructor and InjectionDescriptor declare out Value, so the compiler checks the covariance the engine already relied on.

    Repo-wide: isolatedDeclarations is on for every package that emits declarations, so a public type can always be written down from the source file alone. allowJs is gone from the shared base config — no package has JavaScript sources. @codefast/theme and @codefast/tracking gained explicit annotations on four exported constants to satisfy it; the emitted types are unchanged. @codefast/ui and @codefast/benchmark-viewer opt out for reasons recorded in their configs.

0.5.0

Minor Changes

  • c634b17 Thanks @thevuong! - Rename AppearanceContextType to AppearanceContextValue — the "Type" suffix carried no information (every exported type is a type), while "Value" states the role: the payload provided through AppearanceContext and returned by useAppearance.

  • #547 c7d6818 Thanks @thevuong! - Add a storageKey prop to AppearanceProvider for a fully client-side color scheme path (no server framework, cookie, or loader). When set, the provider reads the persisted preference from localStorage in its initial client render, auto-persists changes there (unless an explicit persistColorScheme is given), and syncs across tabs via the storage event. Paired with <AppearanceScript storageKey> using the same key, the inline script applies the stored value before first paint — so there is no flash even on statically prerendered / CDN-served pages, where an httpOnly cookie can't reach the pre-paint script and a post-mount server round-trip would otherwise flip the appearance.

    AppearanceScript and AppearanceProvider now also mirror the preference (light / dark / automatic) to a data-appearance attribute on <html> — set by the inline script before first paint and kept in sync by the provider. This lets preference-aware UI (e.g. a 3-state theme toggle that shows a distinct "system" icon) render the correct state purely from CSS, with no hydration flash from reading client-only state.

  • #526 fad5a21 Thanks @thevuong! - Add the @codefast/theme/vite plugin. TanStack Start registers the server functions shipped in @codefast/theme/start at the consumer's build time, so the package must not be externalized for SSR nor pre-bundled for the client. The new codefastTheme() plugin applies that configuration automatically, so consumers no longer need to hand-write ssr.noExternal / optimizeDeps.exclude.

Patch Changes

  • #676 641e233 Thanks @thevuong! - Collapse the types and default lanes of package.json#imports from fallback arrays to single strings.

    Node resolves an imports array by taking the first candidate it can parse, without checking that the file exists and without falling through — a specifier whose first candidate is missing throws ERR_MODULE_NOT_FOUND rather than trying the second. ./dist/*/index.js and ./dist/*/index.d.ts could therefore never be reached, so they read as a safety net that does not exist. The source lane keeps its extension candidates, which only tsc and Vite read and both probe.

  • #550 0feb8cd Thanks @thevuong! - Rename the public API to follow Apple's Human Interface Guidelines vocabulary: appearance is the user's preference ("light" | "dark" | "automatic", like macOS System Settings → Appearance), color scheme is the resolved light-or-dark value actually applied (like SwiftUI ColorScheme and CSS color-scheme). Source files are also flattened so each subpath names what it exports.

    Breaking renames:

    • Types: ColorScheme → Appearance; ResolvedColorScheme → ColorScheme; ColorSchemeContextType → AppearanceContextType; colorSchemeSchema → appearanceSchema; colorSchemes → appearances.

    • Hook: useColorScheme() → useAppearance(), returning { appearance, colorScheme, setAppearance, isPending } (was { colorScheme, resolvedColorScheme, setColorScheme, isPending }).

    • Provider props: colorScheme → appearance; persistColorScheme → persistAppearance. Script prop: colorScheme → appearance. Context: ColorSchemeContext → AppearanceContext.

    • Constants: DEFAULT_COLOR_SCHEME → DEFAULT_APPEARANCE ("automatic"); DEFAULT_RESOLVED_COLOR_SCHEME → DEFAULT_COLOR_SCHEME ("dark"). ⚠️ DEFAULT_COLOR_SCHEME still exists but now means the resolved fallback — the prop renames make stale usage a compile error.

    • Subpaths: ./types → ./appearance; ./core/provider → ./appearance-provider; ./core/use-theme → ./use-appearance; ./core/context → ./appearance-context; ./script/theme-script → ./appearance-script; ./utils/system → ./color-scheme; ./utils/dom → ./dom.

    • Persisted values: the default STORAGE_KEY is now "ui-appearance" (was "ui-theme") — returning visitors fall back to "automatic" once. The internal BroadcastChannel is now "appearance-sync" (was "color-scheme-sync").

    Unchanged: AppearanceProvider / AppearanceScript component names, the data-appearance attribute, resolveColorScheme() / getSystemColorScheme() / applyColorScheme() / suppressTransitions().

  • #549 2d16644 Thanks @thevuong! - Remove all server-side handling — the color scheme preference now lives entirely on the client in localStorage.

    Breaking:

    • Removed @codefast/theme/start, @codefast/theme/vite, and @codefast/theme/adapters/tanstack-start/server — the TanStack Start server functions, httpOnly cookie persistence, and the Vite plugin that wired them into consumer builds.
    • Removed AppearanceProvider's ssrColorScheme and syncFromServer props; the provider also no longer re-syncs from a changed colorScheme prop after mount (it is a static fallback now).
    • resolveColorScheme() no longer takes an ssrColorScheme argument; on the server, "automatic" resolves to DEFAULT_RESOLVED_COLOR_SCHEME.
    • Dropped the optional @tanstack/react-start and vite peer dependencies.

    Changed defaults: storageKey now defaults to STORAGE_KEY ("ui-appearance") and colorScheme to DEFAULT_COLOR_SCHEME ("automatic") on both AppearanceScript and AppearanceProvider, so <AppearanceScript /> + <AppearanceProvider> work with zero configuration. persistColorScheme / onPersistError remain for custom persistence.

    Migration: drop the root loader and the codefastTheme() Vite plugin, render the defaults on <html> with suppressHydrationWarning, and let the inline script correct the class before paint — see the README's TanStack Start recipe.

0.5.0-canary.9

Patch Changes

  • #676 641e233 Thanks @thevuong! - Collapse the types and default lanes of package.json#imports from fallback arrays to single strings.

    Node resolves an imports array by taking the first candidate it can parse, without checking that the file exists and without falling through — a specifier whose first candidate is missing throws ERR_MODULE_NOT_FOUND rather than trying the second. ./dist/*/index.js and ./dist/*/index.d.ts could therefore never be reached, so they read as a safety net that does not exist. The source lane keeps its extension candidates, which only tsc and Vite read and both probe.

0.5.0-canary.8

0.5.0-canary.7

0.5.0-canary.6

Minor Changes

  • c634b17 Thanks @thevuong! - Rename AppearanceContextType to AppearanceContextValue — the "Type" suffix carried no information (every exported type is a type), while "Value" states the role: the payload provided through AppearanceContext and returned by useAppearance.

  • #547 c7d6818 Thanks @thevuong! - Add a storageKey prop to AppearanceProvider for a fully client-side color scheme path (no server framework, cookie, or loader). When set, the provider reads the persisted preference from localStorage in its initial client render, auto-persists changes there (unless an explicit persistColorScheme is given), and syncs across tabs via the storage event. Paired with <AppearanceScript storageKey> using the same key, the inline script applies the stored value before first paint — so there is no flash even on statically prerendered / CDN-served pages, where an httpOnly cookie can't reach the pre-paint script and a post-mount server round-trip would otherwise flip the appearance.

    AppearanceScript and AppearanceProvider now also mirror the preference (light / dark / automatic) to a data-appearance attribute on <html> — set by the inline script before first paint and kept in sync by the provider. This lets preference-aware UI (e.g. a 3-state theme toggle that shows a distinct "system" icon) render the correct state purely from CSS, with no hydration flash from reading client-only state.

  • #526 fad5a21 Thanks @thevuong! - Add the @codefast/theme/vite plugin. TanStack Start registers the server functions shipped in @codefast/theme/start at the consumer's build time, so the package must not be externalized for SSR nor pre-bundled for the client. The new codefastTheme() plugin applies that configuration automatically, so consumers no longer need to hand-write ssr.noExternal / optimizeDeps.exclude.

Patch Changes

  • #550 0feb8cd Thanks @thevuong! - Rename the public API to follow Apple's Human Interface Guidelines vocabulary: appearance is the user's preference ("light" | "dark" | "automatic", like macOS System Settings → Appearance), color scheme is the resolved light-or-dark value actually applied (like SwiftUI ColorScheme and CSS color-scheme). Source files are also flattened so each subpath names what it exports.

    Breaking renames:

    • Types: ColorScheme → Appearance; ResolvedColorScheme → ColorScheme; ColorSchemeContextType → AppearanceContextType; colorSchemeSchema → appearanceSchema; colorSchemes → appearances.

    • Hook: useColorScheme() → useAppearance(), returning { appearance, colorScheme, setAppearance, isPending } (was { colorScheme, resolvedColorScheme, setColorScheme, isPending }).

    • Provider props: colorScheme → appearance; persistColorScheme → persistAppearance. Script prop: colorScheme → appearance. Context: ColorSchemeContext → AppearanceContext.

    • Constants: DEFAULT_COLOR_SCHEME → DEFAULT_APPEARANCE ("automatic"); DEFAULT_RESOLVED_COLOR_SCHEME → DEFAULT_COLOR_SCHEME ("dark"). ⚠️ DEFAULT_COLOR_SCHEME still exists but now means the resolved fallback — the prop renames make stale usage a compile error.

    • Subpaths: ./types → ./appearance; ./core/provider → ./appearance-provider; ./core/use-theme → ./use-appearance; ./core/context → ./appearance-context; ./script/theme-script → ./appearance-script; ./utils/system → ./color-scheme; ./utils/dom → ./dom.

    • Persisted values: the default STORAGE_KEY is now "ui-appearance" (was "ui-theme") — returning visitors fall back to "automatic" once. The internal BroadcastChannel is now "appearance-sync" (was "color-scheme-sync").

    Unchanged: AppearanceProvider / AppearanceScript component names, the data-appearance attribute, resolveColorScheme() / getSystemColorScheme() / applyColorScheme() / suppressTransitions().

  • #549 2d16644 Thanks @thevuong! - Remove all server-side handling — the color scheme preference now lives entirely on the client in localStorage.

    Breaking:

    • Removed @codefast/theme/start, @codefast/theme/vite, and @codefast/theme/adapters/tanstack-start/server — the TanStack Start server functions, httpOnly cookie persistence, and the Vite plugin that wired them into consumer builds.
    • Removed AppearanceProvider's ssrColorScheme and syncFromServer props; the provider also no longer re-syncs from a changed colorScheme prop after mount (it is a static fallback now).
    • resolveColorScheme() no longer takes an ssrColorScheme argument; on the server, "automatic" resolves to DEFAULT_RESOLVED_COLOR_SCHEME.
    • Dropped the optional @tanstack/react-start and vite peer dependencies.

    Changed defaults: storageKey now defaults to STORAGE_KEY ("ui-appearance") and colorScheme to DEFAULT_COLOR_SCHEME ("automatic") on both AppearanceScript and AppearanceProvider, so <AppearanceScript /> + <AppearanceProvider> work with zero configuration. persistColorScheme / onPersistError remain for custom persistence.

    Migration: drop the root loader and the codefastTheme() Vite plugin, render the defaults on <html> with suppressHydrationWarning, and let the inline script correct the class before paint — see the README's TanStack Start recipe.

1.0.0-canary.7

1.0.0-canary.6

0.5.0-canary.5

0.5.0-canary.4

Minor Changes

  • c634b17 Thanks @thevuong! - Rename AppearanceContextType to AppearanceContextValue — the "Type" suffix carried no information (every exported type is a type), while "Value" states the role: the payload provided through AppearanceContext and returned by useAppearance.

0.5.0-canary.3


Showing the latest 20 releases; the full changelog on GitHub has 30 more releases.