@todayai-labs/tck-host-utils
Pure utility functions for product Hosts and development tooling
@todayai-labs/tck-host-utils contains deterministic helpers that a product Host and TCK
Preview can share without depending on the React Host runtime. It does not own browser
lifecycle, timers, locale detection, or time-zone detection. Callers pass those inputs
explicitly.
Card locale negotiation
import { resolveCardLocale } from '@todayai-labs/tck-host-utils'
const locale = resolveCardLocale({
cardSupportedLocales: manifest.locale?.supported,
userPreferredLocales: ['zh-TW', 'en-US'],
cardDefaultLocale: manifest.locale?.default,
hostFallbackLocale: 'en',
})The return value is a canonical language tag. For a declared card, it always belongs to the
canonicalized supported list. Preferences are tried from first to last using FormatJS best-fit
matching. A preference with an explicit script, or a script inferred from its region, excludes
candidates with an incompatible specified script. Explicit script wins over region inference:
zh-Hans-TW can match zh-Hans. Language-only candidates such as zh remain eligible without
asserting a script. Matching does not rewrite the card's declaration.
['en-GB', 'fr']against['en-US', 'fr']selectsen-US: preference order wins over a later exact match.['zh-TW', 'en-US']against['zh-Hans', 'en']selectsen: incompatible scripts are not treated as interchangeable.- No match, or an empty preference list, returns
cardDefaultLocale. - Only a card with both card fields omitted uses
hostFallbackLocale. This is Host policy for unknown content, not evidence that the card actually supports that language.
Provide both card fields together. Supported languages must be non-empty and unique after
canonicalization, and the card default must be a canonical member. Invalid card declarations
throw RangeError instead of falling through to the Host fallback. Malformed tags in the active
branch also throw; the unused Host fallback is not consulted for declared cards, and user
preferences are not consulted for undeclared cards. No browser language or process default is read.
The runtime requires Intl.getCanonicalLocales and Intl.Locale with maximize() support.
This is a pure selection helper; consumers decide how to apply its result. It does not translate
card content or provide a runtime useLocale hook.
Updated-at slot formatting
import { formatUpdatedAtSlotValue, nextUpdatedAtRefreshAt } from '@todayai-labs/tck-host-utils'
const timestamp = '2026-09-03T14:14:30Z'
const now = Date.now()
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const updatedAt = formatUpdatedAtSlotValue(timestamp, {
now,
locale: navigator.languages,
timeZone,
})
const nextRefreshAt = nextUpdatedAtRefreshAt(timestamp, { now, timeZone })The formatter applies these priorities:
| Condition | Default English label |
|---|---|
| Less than one minute | Updated now |
| Less than one hour | Updated 18 min ago |
| Same natural day | Updated at 14:32 |
| Previous natural day | Updated yesterday at 23:10 |
| Earlier | Updated Sep 1 at 09:05 |
Natural-day comparisons use the supplied IANA time zone, not an elapsed 24/48-hour test.
Invalid timestamps return null. A product Host should keep one shared clock, recompute at
nextRefreshAt, and also recompute after foreground/focus, locale changes, or time-zone
changes. The Preview package contains its own shared-clock adapter; the utility package
deliberately does not.
@todayai-labs/tck-host
Browser-side host runtime. Manifest registry, widget loader, layout grid, patch dispatcher, agent channel, two-tier mount surface.
@todayai-labs/tck-host-native
Bundle-artifact package for non-Vite native hosts (macOS / iOS / iPadOS / visionOS). Ships dist/ with harness.html + importmap-resolved shared deps for WebView consumers.