Register
Latest release
2026-10-04
First commit
2026-09-29

Packages

Library · TypeScript · npm

The npm packages under this site, Taverla, Séance and on-record, typed as far as the compiler goes.

Small TypeScript packages with no third-party runtime dependency, published on npm, each lifted out of a project once a second one needed it. One translates, and knows at compile time what each message asks for; one says whether something worked, without throwing and without null; the others keep the chosen theme from flashing, take the throw out of localStorage and the clipboard, and share the reset, the focus ring and the compiler and linter settings every project starts from.

  • TypeScript
  • React
  • Intl
  • Vitest
  • Biome
  • Changesets
  • GitHub Actions
Documentation (opens in a new tab)Source (opens in a new tab)The 11 packages
Published4 · X · 2026
@adrienlcp/i18n
Refused3 errors
const EN = defineDictionary({ greeting: 'Hello {name}' })

translate('greeting', { name: 'Ada' })

translate('greeting')
Refused by the compiler: Expected 2 arguments, but got 1

translate('greeting', { nom: 'Ada' })
Refused by the compiler: 'nom' does not exist in type '{ name: string }'

translate('greting', { name: 'Ada' })
Refused by the compiler: not assignable to '"greeting"'

A message’s arguments are read off the message itself: leave out {name} and the call does not compile. No code generation, no build plugin.

Packages
11under @adrienlcp
Latest release
2026-10-04to npm, with provenance
Commits
64on main, read 2026-10-04
Coverage
96%lines run by the tests of every package, read 2026-10-02
Installed by
6 appsTaverla, on-record, Séance, Analytics, Scoreboard, This site

As the compiler reads it

Set the way an editor shows it: a wavy line is a build that refuses, a violet line is what the compiler works out on its own.

  1. @adrienlcp/result
    Compiles0 errors
    const parse = (input: string): Result<number, 'not_a_number'> => {
      const value = Number(input)
      return Number.isNaN(value)
        ? Result.failure('not_a_number')
        : Result.success(value)
    }
    
    const result = parse(raw)
    if (result.status === 'failure') return result.error
    Worked out by the compiler: 'not_a_number'
    result.data
    Worked out by the compiler: number
    

    Result fits in thirty lines. A success that carries nothing has no data key at all, so nobody reads undefined off it.

  2. export const themeStore = createThemePreferenceStore({
      storageKey: 'app:theme'
    })
    
    // vite.config.ts: runs before the first paint
    plugins: [themePreferencePlugin(themeStore)]
    
    themeStore.setPreference('dark')
    Worked out by the compiler: <html data-theme="dark">
    Worked out by the compiler: dark theme-color: media="all"
    Worked out by the compiler: light theme-color: media="not all"
    

    A phone paints its toolbar from the theme-color tags, which follow the system, not the visitor. The theme package rewrites their media query before the first paint, so a dark choice on a light phone gets a dark toolbar too.

  3. @adrienlcp/safe-storage
    Compiles0 errors
    const locale = readRecognizedText({
      key: 'app:locale',
      isRecognized: isLocale
    })
    
    if (locale.status === 'failure') {
      locale.error
      Worked out by the compiler: 'unavailable' | 'unrecognized'
    } else {
      locale.data
      Worked out by the compiler: 'en' | 'fr' | null
    }
    

    Safari’s private window once threw on every localStorage write; safe-storage returns a Result there instead, as for a full quota or a value an older version stored.

What shipped

  1. The French dictionary is typed against the English one, down to each placeholder: a missing key or a {nom} written for {name} fails the build instead of showing on screen.
  2. Every rule it enforces is a compile error, so those tests are written as types and checked by tsc.
  3. browser copies to the clipboard even over plain HTTP, where the Clipboard API does not exist.
  4. Released with Changesets and published from GitHub Actions with npm provenance, never from a laptop.
  5. This site, Taverla, Séance and on-record install them from npm, down to their TypeScript and Biome configs.

The 11

Each at its version on npm, and which app installs it.

  1. 2026-10-03 · 11 packages released to npm, with provenanceDocumentation (opens in a new tab)github.com/AdrienLcp/packages (opens in a new tab)
    1. @adrienlcp/react-router (opens in a new tab)

      The router provider every app takes.

      0.1.1Used by TaverlaUsed by on-recordUsed by SéanceNot used by AnalyticsNot used by ScoreboardUsed by This siteShipped
    2. @adrienlcp/i18n (opens in a new tab)

      Translates, and knows at compile time what each message asks for.

      0.3.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsNot used by ScoreboardUsed by This siteShipped
    3. @adrienlcp/theme-preference (opens in a new tab)

      Keeps the chosen theme from flashing, down to the phone’s toolbar.

      0.3.0Used by TaverlaUsed by on-recordUsed by SéanceNot used by AnalyticsNot used by ScoreboardUsed by This siteShipped
    4. @adrienlcp/browser (opens in a new tab)

      Copies to the clipboard even over plain HTTP, where the Clipboard API does not exist.

      0.1.1Used by TaverlaUsed by on-recordUsed by SéanceNot used by AnalyticsNot used by ScoreboardUsed by This siteShipped
    5. @adrienlcp/react (opens in a new tab)

      The context helpers every React app starts from.

      0.3.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    6. @adrienlcp/react-aria (opens in a new tab)

      The focus ring and class composition over react-aria-components.

      0.1.0Used by TaverlaUsed by on-recordNot used by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    7. @adrienlcp/styles (opens in a new tab)

      The reset and shared styles every project starts from.

      0.5.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    8. @adrienlcp/result (opens in a new tab)

      Says whether something worked, without throwing and without null.

      0.1.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    9. @adrienlcp/safe-storage (opens in a new tab)

      Takes the throw out of localStorage and returns a Result instead.

      0.1.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    10. @adrienlcp/tsconfig (opens in a new tab)

      The compiler settings every project extends.

      0.1.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped
    11. @adrienlcp/biome-config (opens in a new tab)

      The linter and formatter settings every project extends.

      0.1.0Used by TaverlaUsed by on-recordUsed by SéanceUsed by AnalyticsUsed by ScoreboardUsed by This siteShipped

History

Straight from the git log, abridged: 8 of 64 commits, newest first.

  1. Darken the muted ink until it reads at WCAG AA on every surface, checked by a test
  2. Format a Temporal ZonedDateTime in {x:date}, in its own zone and calendar
  3. Check colour token contrast against WCAG from a test, through @adrienlcp/styles/contrast
  4. Add the documentation site, built from the packages' own sources
  5. Animate exits through CSS, and animate navigations with React's ViewTransition
  6. Add container query mixins and let sizes transition to keywords
  7. Hold back a package npm has never seen instead of failing the release
  8. Publish the i18n and result packagesFirst commit

Your team, 2026

I am looking for my next job. Write to me, or take the CV: a designed one, and a plain one for applicant tracking systems.