Skip to content

tidy/up for review#29

Open
magmacomputing wants to merge 6 commits intomainfrom
tidy-ups
Open

tidy/up for review#29
magmacomputing wants to merge 6 commits intomainfrom
tidy-ups

Conversation

@magmacomputing
Copy link
Copy Markdown
Owner

@magmacomputing magmacomputing commented May 10, 2026

small code-fixes

Summary by CodeRabbit

  • New Features

    • Fractional-resolution numeric timestamp parsing with deterministic BigInt math, 24:00 midnight-overflow support, branded ISOString, and a chainable AliasContext API.
  • Bug Fixes

    • Stability and correctness fixes for epoch detection/parsing, NaN/non-finite handling, alias lifecycle/state isolation, registry reset behavior, and zoned-datetime mutation ordering.
  • Documentation

    • Changelog, migration guide, parsing and config docs updated (timeStamp units and epoch behavior).
  • Chores

    • Version bumps and dependency updates.
  • Tests

    • Added suites for fractional resolution, timestamp config, 24:00, and alias chaining.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

📝 Walkthrough

Walkthrough

This release updates Tempo to 2.9.3: assertion predicates move to unknown-based guards, a branded ISOString and AliasContext API are added, numeric epoch resolution is unit-aware with 9-digit fractional normalization, alias resolution order and parse return shapes are changed, manifests and docs updated, and tests added/adjusted.

Changes

Tempo 2.9.3: Type Hardening, Fractional Resolution & Alias Context

Layer / File(s) Summary
Type System & Public Contracts
packages/tempo/src/tempo.type.ts
Adds branded ISOString, expands DateTime, introduces AliasContext and AliasFunction, tightens Logic, and adds Internal.State.userProvidedKeys.
Library Assertion Predicates
packages/library/src/common/assertion.library.ts
Refactors many type-guard functions from generic T/Extract<>-based signatures to unknown-based parameters with concrete return predicates.
Temporal Library v8 Harmony
packages/library/src/common/temporal.library.ts
getTemporalIds uses an intermediate raw object when extracting timezone/calendar properties.
Engine: Numeric Epoch Handling
packages/tempo/src/engine/engine.composer.ts
Consolidates Number/BigInt epoch conversion to unit-derived scales (ss/ms/us/ns); fractional Numbers normalized to 9-digit nanoseconds; BigInt now respects configured unit; emits onResult and creates Instant from epoch nanos.
Engine: Prefix Lexer Overloads
packages/tempo/src/engine/engine.lexer.ts
Adds typed overloads for prefix() and simplifies normalization; num()/parseWeekday/parseDate updated to use normalized tokens and const locals.
Engine: Alias Context & Resolution
packages/tempo/src/engine/engine.normalizer.ts
Adds getAliasContext() host with chainable add()/set() that update ZonedDateTime; reorders alias resolution (event/slick early, period later); resolveAliases gains optional filter.
Parse Module: Conform & Layout
packages/tempo/src/module/module.parse.ts
parse/conform now return structured { type, value, zone, calendar } shapes in many branches; parseLayout adds Empty branch and rewrites epoch short-circuit logic.
Support: Init, Defaults & Utilities
packages/tempo/src/support/support.init.ts, packages/tempo/src/support/support.default.ts, packages/tempo/src/support/support.util.ts
init() adds userProvidedKeys; extendState() records provided keys and validates timeStamp units (`ss
Tempo Class & Registry
packages/tempo/src/tempo.class.ts
Normalize discovery.timezones via Intl.Locale(...).baseName; refactor discovery.intl/relativeTime merge; append legacy term into terms; clear root aliasEngine evt/per on registry reset.
Engine Alias Registration
packages/tempo/src/engine/engine.alias.ts
Skip duplicate alias registrations by checking baseWord and identical existing entries; conditional overwrite of collision-word mapping.
Manifest & Package Configuration
package.json, packages/library/package.json, packages/tempo/package.json
Bump versions to 2.9.3, update selected devDependencies (TypeScript, Vitest, Rollup), adjust REPL scripts; update README API link.
Documentation & Release Notes
packages/tempo/CHANGELOG.md, packages/tempo/doc/*, packages/tempo/doc/migration-guide.md, packages/tempo/plan/*
Add v2.9.3 changelog and release notes, numeric & epoch parsing docs, migration note for BigInt unit change, and a licensing architecture plan.
Tests: New Features & Type Updates
packages/tempo/test/**
Add tests for numeric fractional resolution, timestamp configuration, 24:00 overflow, functional alias chaining, epoch parsing; update tests to use AliasContext and adjusted plugin install signature.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

"A rabbit hops, reporting cheer, 🐰
Unknown guards make types more clear,
Fractions round to nanos, neat and bright,
AliasContext guides chained insight,
Tests tumble in — the release takes flight."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'tidy/up for review' is vague and non-descriptive, using generic phrasing that does not convey meaningful information about the substantive changes in this pull request. Revise the title to reflect the actual primary changes, such as 'v2.9.3 release: numeric parsing, alias context hardening, and dependency updates' or similar to clarify the main focus.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tidy-ups

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (5)
packages/tempo/test/issues/issue-fixes.test.ts (1)

2-2: ⚡ Quick win

Use import type for AliasContext to match sibling test files and stay safe under verbatimModuleSyntax.

The other tests in this PR (relative_date.test.ts, sandbox-factory.test.ts) consistently import it as import type. This file is the only outlier.

♻️ Diff
-import { AliasContext } from '#tempo/tempo.type.js';
+import type { AliasContext } from '#tempo/tempo.type.js';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/test/issues/issue-fixes.test.ts` at line 2, Replace the value
import of AliasContext with a type-only import to match the other tests: change
the import of AliasContext from '#tempo/tempo.type.js' to a type import (i.e.,
use "import type { AliasContext } ...") so the test uses a type-only reference
and remains compatible with verbatimModuleSyntax; update the import statement
where AliasContext is referenced in the test file to the type-only form.
packages/tempo/src/engine/engine.normalizer.ts (1)

112-134: 💤 Low value

filter param via prefix match is fine for the current key shape; consider an exact match against the alias-engine's group name to be future-proof.

Today the matcher relies on regex group keys being shaped like evt\d+_\d+ / per\d+_\d+ / literal slk, so key.startsWith(f) coincides with intent. If anyone later adds a different group key whose name happens to start with evt/per, it would unintentionally pass the filter.

You could resolve the alias's actual type (via aliasEngine.getAlias(key)?.type) before applying the filter, but skip if not worth the indirection in the hot path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.normalizer.ts` around lines 112 - 134, The
current prefix-based filter in resolveAliases risks false positives; change the
check inside resolveAliases to perform an exact match against the alias-engine's
group/type: for each key, resolve const alias = aliasEngine?.getAlias(key) (or
similar API) and if filter is provided only continue when filter.some(f => f ===
key || (alias && (alias.type === f || alias.groupName === f))); if aliasEngine
is missing fall back to the existing key === f || key.startsWith(f) behavior to
preserve hot-path performance. Ensure you reference resolveAliases, aliasEngine
and getAlias in the change.
packages/tempo/src/tempo.class.ts (1)

364-374: ⚡ Quick win

Shallow { ...intl.relativeTime, ...discovery.relativeTime } silently drops a function-typed relativeTime.

If discovery.intl.relativeTime is a function (now permitted by the expanded IntlOptions) and discovery.relativeTime is an object, line 370 spreads the function — yielding only its enumerable own properties (typically {}) — so the function is dropped without warning. Likely an unintended interaction between the two sibling shorthand inputs.

♻️ Proposed handling: don't merge across function/object boundaries
 if (discovery.intl || discovery.relativeTime) {
     const intl: t.IntlOptions = { ...discovery.intl };
     if (discovery.relativeTime) {
         if (typeof discovery.relativeTime === 'function') {
             intl.relativeTime = discovery.relativeTime;
-        } else {
-            intl.relativeTime = { ...intl.relativeTime, ...discovery.relativeTime };
+        } else {
+            // If the inherited shorthand is a function, the explicit object replaces it
+            // rather than being merged into a coerced empty object.
+            intl.relativeTime = typeof intl.relativeTime === 'function'
+                ? { ...discovery.relativeTime }
+                : { ...intl.relativeTime, ...discovery.relativeTime };
         }
     }
     shape.config.intl = { ...shape.config.intl, ...intl };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/tempo.class.ts` around lines 364 - 374, The code currently
merges intl.relativeTime by shallow-spreading which can drop a function value;
change the merging logic in the block that handles discovery.intl /
discovery.relativeTime so you never attempt to spread a function. Concretely: if
discovery.relativeTime is a function, set intl.relativeTime =
discovery.relativeTime; else if intl.relativeTime (from discovery.intl) is a
function and discovery.relativeTime is an object, keep intl.relativeTime as the
function (do not merge); otherwise (both are objects or undefined) merge with {
...intl.relativeTime, ...discovery.relativeTime } and then assign
shape.config.intl = { ...shape.config.intl, ...intl }. Ensure you reference the
existing symbols discovery.intl, discovery.relativeTime, intl.relativeTime, and
shape.config.intl when making the change.
packages/tempo/doc/tempo.parse.md (1)

114-138: 💤 Low value

Resolution Context section now matches the actual AliasContext surface.

Replacing this.with(...) with this.set({ hour: 22, minute: 0, second: 0 }) accurately reflects that the alias host exposes set/add/toNow/toDateTime (not Temporal's .with). The accessor list (hh/mi/ss) is correct.

Minor optional polish (skip if not worth it): the host also exposes yy/mm/dd/tz/cal/config — a one-line addition would make the doc exhaustive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/doc/tempo.parse.md` around lines 114 - 138, Update the
"Functional Alias Context" docs to match the real AliasContext API: replace any
mention of this.with(...) with this.set(...) and explicitly list the available
methods this.add(duration), this.set(input), this.toNow(), this.toDateTime() and
accessors this.hh / this.mi / this.ss; also ensure the example alias functions
call this.set(...) and this.add(...) (referencing AliasContext, the example
alias strings 'bedtime' and 'meeting', and the methods this.set and this.add).
Optionally append the remaining context accessors yy/mm/dd/tz/cal/config in one
line to make the surface exhaustive.
packages/tempo/test/engine/timestamp_config.test.ts (1)

14-17: ⚡ Quick win

Remove draft/internal-note comments from committed test code.

Line 14–Line 17 read like temporary author notes and make intent harder to follow. Keep only behavior-focused comments.

Suggested cleanup
-		// 10 digits in ms mode should currently NOT short-circuit unless we improve the logic
-		// But wait, I'll update it to expect 1970 if it WAS treated as ms
-		// OR I'll use a 13-digit number for the ms test
+		// Use a millisecond-scale epoch value for ms mode assertions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/test/engine/timestamp_config.test.ts` around lines 14 - 17,
The test contains draft/internal-note comments around the msVal declaration
(variable msVal = 1715900000000) that should be removed; replace them with a
concise, behavior-focused comment explaining the purpose of msVal (e.g., "use a
13-digit millisecond timestamp for ms-mode test") and ensure the msVal variable
and its usage in the timestamp_config test remain unchanged; edit the block
around msVal to remove the temporary author notes and keep only a single clear
comment describing expected behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tempo/CHANGELOG.md`:
- Line 16: Update the CHANGELOG entry that currently lists "Vitest 2.1.9" under
the "**Dependency Refresh**" bullet to the correct version "Vitest 4.1.5";
locate the line containing the text "Vitest 2.1.9" (the dependency-refresh
sentence) and replace that version string with "Vitest 4.1.5" so the changelog
accurately reflects the latest stable Vitest release.

In `@packages/tempo/src/engine/engine.composer.ts`:
- Around line 89-98: The fractional-number branch currently uses
value.toString().split('.') which breaks on scientific notation and throws in
BigInt; replace that string-based parsing with numeric extraction: compute const
absVal = Math.abs(value), derive wholeNumber = BigInt(Math.trunc(absVal)),
derive fractionDigits = BigInt(Math.round((absVal - Math.trunc(absVal)) *
1_000_000_000)), normalize fractionDigits to be in 0..999_999_999 and if
rounding causes fractionDigits === 1_000_000_000 increment wholeNumber and set
fractionDigits to 0, then compute nano = (wholeNumber * scale) + (fractionDigits
* scale / 1_000_000_000n) and finally apply sign if value < 0; update the code
paths that assign whole, fraction and nano (symbols: value, whole,
fraction/fractionDigits, scale, nano) accordingly to avoid using
value.toString().

In `@packages/tempo/src/engine/engine.lexer.ts`:
- Around line 54-60: The overloads for prefix are unsound because passing plural
tokens (t.WEEKDAYS, t.MONTHS) can return plural-form keys but the signatures
force singular return types; update the declarations for the prefix function so
each plural input type returns the matching plural key type (add overloads like
prefix(str: t.WEEKDAYS): t.WEEKDAYS and prefix(str: t.MONTHS): t.MONTHS) and
keep the singular overloads and the generic fallback implementation intact so
the implementation signature still handles any input.

In `@packages/tempo/src/module/module.parse.ts`:
- Around line 255-295: The length gate wrongly prevents valid epoch integers
when a non-default unit is configured; update the isLong calculation in
module.parse.ts so that when state.config.timeStamp !== 'ms' you treat the
numeric string as an epoch candidate regardless of trim.length (i.e., set isLong
= true for non-'ms' units), then keep the existing branches that convert
strings/numbers to BigInt/Number and mark isEpoch; this change touches the
isLong variable and the type branches (type === 'String', type === 'Number', and
the isEpoch handling) so they correctly classify shorter second-based timestamps
like 946684800 as Epoch.
- Around line 186-190: The code in parse (around value.toDateTime() /
getTemporalIds) mutates state.config.timeZone and state.config.calendar, which
overwrites caller overrides for parse(value, { timeZone, calendar }) when the
input is a Tempo; instead compute tz and cal locally from getTemporalIds(res)
but do not assign them into state.config; return the arg with type
'Temporal.ZonedDateTime' and the computed res, and if compose needs the derived
tz/cal pass them in the returned arg's metadata (e.g., attach a local
zone/calendar property) rather than mutating state.config so parse for Tempo
respects caller-provided timeZone/calendar; update references to
state.config.timeZone / calendar in compose to prefer caller overrides and then
fall back to any tz/cal passed on the returned arg.

In `@packages/tempo/src/support/support.default.ts`:
- Around line 24-25: The hour alternation in the time and clock regexes (keys
time and clock in support.default.ts) is too broad (2[0-4]) and lets invalid
times like 24:30 pass; tighten the patterns so normal hours use 0–23 (2[0-3])
and only allow the literal end-of-day “24:00” (with optional :00 and fractional
seconds for clock) as a special case; update the two regexes and any equivalent
patterns used for Snippet[Token.hh] and Layout[Token.hms] to the constrained
alternation (0[0-9]|1[0-9]|2[0-3]|24:00(?:[:]?00(?:\.\d+)? )?) so only true
24:00 variants are accepted while rejecting 24:30/24:59:59.

In `@packages/tempo/src/support/support.init.ts`:
- Around line 240-242: The timeStamp branch currently calls
setProperty(state.config, optKey, arg.value) without validating units, causing
typos like "mss" to be treated as nanoseconds; update the case 'timeStamp'
handling to validate arg.value against the allowed timestamp/unit set (e.g.,
explicit list of accepted units/formats) before calling setProperty, and
reject/raise an error (or return a validation failure) for invalid values so
downstream numeric parsing cannot silently fallback to ns; locate the logic in
support.init.ts around the case 'timeStamp' and modify the handling there
(referencing case 'timeStamp', setProperty, state.config, optKey, arg.value).

In `@packages/tempo/test/engine/numeric_resolution.test.ts`:
- Around line 43-49: The test mutates global runtime config by calling
Tempo.init({ catch: true }) which can leak into other tests; change the test to
save and restore the previous config (or call Tempo.init with the prior
settings) using setup/teardown around this test so the global state is restored
after it runs, locating the call to Tempo.init in the 'should reject NaN with
custom error' test and ensuring after the test finishes the original Tempo.init
configuration is reapplied (or use beforeEach/afterEach to reset Tempo.init) to
avoid cross-test contamination.

In `@packages/tempo/test/issues/24-hour-overflow.test.ts`:
- Around line 16-24: Read the current time once into a single anchor (e.g.,
const anchor = Temporal.Now.zonedDateTimeISO()), compute the expected "tomorrow"
from that anchor, and then use that same anchor when producing the actual value
so both sides use the same reference; specifically, update the test to get
anchor before calling new Tempo('24:00'), compute tomorrow from anchor, and
either pass anchor into the Tempo constructor or call t.toDateTime(anchor)
(whichever API Tempo provides) so the test compares values derived from the
identical anchor instead of two different "now" calls.

---

Nitpick comments:
In `@packages/tempo/doc/tempo.parse.md`:
- Around line 114-138: Update the "Functional Alias Context" docs to match the
real AliasContext API: replace any mention of this.with(...) with this.set(...)
and explicitly list the available methods this.add(duration), this.set(input),
this.toNow(), this.toDateTime() and accessors this.hh / this.mi / this.ss; also
ensure the example alias functions call this.set(...) and this.add(...)
(referencing AliasContext, the example alias strings 'bedtime' and 'meeting',
and the methods this.set and this.add). Optionally append the remaining context
accessors yy/mm/dd/tz/cal/config in one line to make the surface exhaustive.

In `@packages/tempo/src/engine/engine.normalizer.ts`:
- Around line 112-134: The current prefix-based filter in resolveAliases risks
false positives; change the check inside resolveAliases to perform an exact
match against the alias-engine's group/type: for each key, resolve const alias =
aliasEngine?.getAlias(key) (or similar API) and if filter is provided only
continue when filter.some(f => f === key || (alias && (alias.type === f ||
alias.groupName === f))); if aliasEngine is missing fall back to the existing
key === f || key.startsWith(f) behavior to preserve hot-path performance. Ensure
you reference resolveAliases, aliasEngine and getAlias in the change.

In `@packages/tempo/src/tempo.class.ts`:
- Around line 364-374: The code currently merges intl.relativeTime by
shallow-spreading which can drop a function value; change the merging logic in
the block that handles discovery.intl / discovery.relativeTime so you never
attempt to spread a function. Concretely: if discovery.relativeTime is a
function, set intl.relativeTime = discovery.relativeTime; else if
intl.relativeTime (from discovery.intl) is a function and discovery.relativeTime
is an object, keep intl.relativeTime as the function (do not merge); otherwise
(both are objects or undefined) merge with { ...intl.relativeTime,
...discovery.relativeTime } and then assign shape.config.intl = {
...shape.config.intl, ...intl }. Ensure you reference the existing symbols
discovery.intl, discovery.relativeTime, intl.relativeTime, and shape.config.intl
when making the change.

In `@packages/tempo/test/engine/timestamp_config.test.ts`:
- Around line 14-17: The test contains draft/internal-note comments around the
msVal declaration (variable msVal = 1715900000000) that should be removed;
replace them with a concise, behavior-focused comment explaining the purpose of
msVal (e.g., "use a 13-digit millisecond timestamp for ms-mode test") and ensure
the msVal variable and its usage in the timestamp_config test remain unchanged;
edit the block around msVal to remove the temporary author notes and keep only a
single clear comment describing expected behavior.

In `@packages/tempo/test/issues/issue-fixes.test.ts`:
- Line 2: Replace the value import of AliasContext with a type-only import to
match the other tests: change the import of AliasContext from
'#tempo/tempo.type.js' to a type import (i.e., use "import type { AliasContext }
...") so the test uses a type-only reference and remains compatible with
verbatimModuleSyntax; update the import statement where AliasContext is
referenced in the test file to the type-only form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ed1f9ba-6a6b-48d5-b8be-6fb74e1e8c5c

📥 Commits

Reviewing files that changed from the base of the PR and between 55b3058 and fdbfe9f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • package.json
  • packages/library/package.json
  • packages/library/src/common/assertion.library.ts
  • packages/library/src/common/temporal.library.ts
  • packages/tempo/CHANGELOG.md
  • packages/tempo/doc/releases/index.md
  • packages/tempo/doc/releases/v2.x.md
  • packages/tempo/doc/releases/v3.x.md
  • packages/tempo/doc/tempo.parse.md
  • packages/tempo/package.json
  • packages/tempo/src/engine/engine.composer.ts
  • packages/tempo/src/engine/engine.lexer.ts
  • packages/tempo/src/engine/engine.normalizer.ts
  • packages/tempo/src/module/module.parse.ts
  • packages/tempo/src/support/support.default.ts
  • packages/tempo/src/support/support.init.ts
  • packages/tempo/src/tempo.class.ts
  • packages/tempo/src/tempo.type.ts
  • packages/tempo/test/core/sandbox-factory.test.ts
  • packages/tempo/test/engine/functional_alias_chaining.test.ts
  • packages/tempo/test/engine/numeric_resolution.test.ts
  • packages/tempo/test/engine/timestamp_config.test.ts
  • packages/tempo/test/instance/relative_date.test.ts
  • packages/tempo/test/issues/24-hour-overflow.test.ts
  • packages/tempo/test/issues/issue-fixes.test.ts
  • packages/tempo/test/plugins/plugin.test.ts
  • packages/tempo/test/plugins/reactive_registration.test.ts

Comment thread packages/tempo/CHANGELOG.md Outdated
Comment thread packages/tempo/src/engine/engine.composer.ts Outdated
Comment thread packages/tempo/src/engine/engine.lexer.ts
Comment thread packages/tempo/src/module/module.parse.ts Outdated
Comment thread packages/tempo/src/module/module.parse.ts
Comment thread packages/tempo/src/support/support.default.ts Outdated
Comment thread packages/tempo/src/support/support.init.ts Outdated
Comment thread packages/tempo/test/engine/numeric_resolution.test.ts Outdated
Comment thread packages/tempo/test/issues/24-hour-overflow.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/tempo/src/tempo.class.ts (1)

365-373: 💤 Low value

Consider documenting precedence when both intl.relativeTime and relativeTime are provided.

The current logic silently ignores discovery.relativeTime (when it's an object) if intl.relativeTime is already a function. While this prevents overwriting a function with an object (per the AI summary), users might be surprised if they provide both. A debug log or comment clarifying the precedence would help maintainability.

📝 Optional: Add clarifying comment
 		const intl: t.IntlOptions = { ...discovery.intl };
 		if (discovery.relativeTime) {
 			if (typeof discovery.relativeTime === 'function') {
 				intl.relativeTime = discovery.relativeTime;
 			} else if (!(typeof intl.relativeTime === 'function')) {
+				// Only merge object form if intl.relativeTime is not already a function
 				intl.relativeTime = { ...intl.relativeTime, ...discovery.relativeTime };
 			}
+			// Note: if intl.relativeTime is a function and discovery.relativeTime is an object,
+			// the object is intentionally ignored to preserve the function
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/tempo.class.ts` around lines 365 - 373, The code that
merges discovery.relativeTime into intl (in Tempo class code handling
shape.config.intl) silently skips discovery.relativeTime when intl.relativeTime
is already a function; add a concise clarifying comment and/or a debug log that
documents the precedence (i.e., "if intl.relativeTime is a function, it takes
precedence over discovery.relativeTime object") near the block that checks
typeof discovery.relativeTime and typeof intl.relativeTime, and optionally emit
a debug/processLogger.debug call when both are present to make the behavior
explicit for maintainers.
packages/tempo/src/engine/engine.composer.ts (1)

82-119: 💤 Low value

Fractional Number → epoch nanoseconds: addresses the prior scientific-notation crash.

Replacing the old value.toString().split('.') parse with Math.trunc + Math.round correctly avoids the BigInt('1e-9') failure mode that was raised previously, and the carry-on-rollover guard (fractionDigits === 1_000_000_000n) is the right safety net for Math.round overshoot. The (whole * 1e9 + frac) * scale / 1e9 formulation produces exact results for ss/ms/us and degrades cleanly to truncation for ns (where sub-nanosecond input has nowhere to go anyway).

One small follow-up if you want to tighten readability: lift scale and matchName out of the chained ternaries into a small lookup, since the same unit ladder is repeated on lines 92 and 114.

♻️ Optional readability tweak
-				// 📏 Resolve multipliers for nanosecond conversion
-				const scale = unit === 'ss' ? 1_000_000_000n : (unit === 'ms' ? 1_000_000n : (unit === 'us' ? 1_000n : 1n));
+				// 📏 Resolve multipliers for nanosecond conversion
+				const UNIT_TABLE = { ss: { scale: 1_000_000_000n, name: 'Seconds' }, ms: { scale: 1_000_000n, name: 'Milliseconds' }, us: { scale: 1_000n, name: 'Microseconds' }, ns: { scale: 1n, name: 'Nanoseconds' } } as const;
+				const { scale, name: matchName } = UNIT_TABLE[unit];
 				let nano: bigint;
 ...
-				// 🏷️ Log Result Metadata
-				const matchName = unit === 'ss' ? 'Seconds' : (unit === 'ms' ? 'Milliseconds' : (unit === 'us' ? 'Microseconds' : 'Nanoseconds'));
 				onResult?.({ type, value, match: matchName });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.composer.ts` around lines 82 - 119, The code
correctly handles fractional numbers but repeats the unit-to-scale and
unit-to-label ternaries; refactor by creating a small lookup (e.g., map or const
object) keyed by unit that provides both scale and matchName, then replace the
chained ternaries used to compute `scale` and `matchName` with lookups; update
uses of `scale` and `matchName` in the Number/BigInt case (around the `scale`
declaration and the `onResult` call) to pull values from the lookup so the logic
in that block becomes clearer and avoids duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tempo/src/engine/engine.composer.ts`:
- Around line 82-110: Add a v2.9.3 migration note explaining the breaking change
where BigInt inputs now respect the configured unit (the unit variable/option,
default 'ms') instead of being treated as raw nanoseconds; reference the
affected behavior in engine.composer.ts (the BigInt/Number case that multiplies
by scale) and include a concise example showing old vs new behavior and the
explicit workaround: pass an explicit nanosecond timestamp (e.g., construct
Tempo with timeStamp: 'ns') to restore pre-v2.9.3 semantics.
- Around line 24-26: The current use of hasOwn(config, 'timeZone') /
hasOwn(config, 'calendar') in engine.composer.ts is unreliable because init()
assigns system defaults as own properties on state.config (via support.init.ts's
Object.assign and setProperties), so derivedTz/derivedCal are never used; change
initialization or config detection so we can tell user-supplied keys from
defaults—for example, record which keys were explicitly passed in init() (e.g.,
store a Set like userProvidedKeys on state or config) or place defaults on the
prototype chain instead of as own properties; then update the selection logic in
engine.composer.ts (the finalTz/finalCal computation using hasOwn, derivedTz,
derivedCal) to check that new marker (e.g., userProvidedKeys.has('timeZone') or
Object.prototype.hasOwnProperty.call(config, ...) vs prototype check) so
explicit input timezones/calendars (derivedTz/derivedCal) are honored.

In `@packages/tempo/src/engine/engine.lexer.ts`:
- Line 7: Remove the unused Tempo import: delete the `import Tempo from
'#tempo';` statement in engine.lexer.ts since the code uses `isTempo()` from
'#tempo/support' for type checks and does not reference `Tempo`; ensure no other
uses of the `Tempo` symbol remain in this file before committing.

In `@packages/tempo/src/module/module.parse.ts`:
- Around line 38-43: The conform function has a branch (in the
isZonedDateTimeLike + missing-term path) that returns undefined which later
causes compose (and parse) to destructure a nil arg; update that branch (and any
similar bare-undefined returns in conform) to return the standardized shape {
type, value, zone?, calendar? } (matching the other Temporal.ZonedDateTime
returns) so compose’s destructure (const { type, value, … } = arg) always
receives an object; locate the conform function and replace the bare `return
undefined as any` (and the symmetric branch noted near the
isTempo/isZonedDateTimeLike logic) with a proper object that preserves zone and
calendar when available.
- Around line 371-388: The current withState wrapper (function withState ->
inner function using fn and res) unwraps any object with a 'value' key which can
wrongly unwrap plain Groups objects; change the final unwrap logic so it only
returns res.value when res is an object that contains both 'type' and 'value'
keys (i.e., the TypeValue envelope returned by parse/conform/parseLayout),
otherwise return the whole res unchanged.

---

Nitpick comments:
In `@packages/tempo/src/engine/engine.composer.ts`:
- Around line 82-119: The code correctly handles fractional numbers but repeats
the unit-to-scale and unit-to-label ternaries; refactor by creating a small
lookup (e.g., map or const object) keyed by unit that provides both scale and
matchName, then replace the chained ternaries used to compute `scale` and
`matchName` with lookups; update uses of `scale` and `matchName` in the
Number/BigInt case (around the `scale` declaration and the `onResult` call) to
pull values from the lookup so the logic in that block becomes clearer and
avoids duplication.

In `@packages/tempo/src/tempo.class.ts`:
- Around line 365-373: The code that merges discovery.relativeTime into intl (in
Tempo class code handling shape.config.intl) silently skips
discovery.relativeTime when intl.relativeTime is already a function; add a
concise clarifying comment and/or a debug log that documents the precedence
(i.e., "if intl.relativeTime is a function, it takes precedence over
discovery.relativeTime object") near the block that checks typeof
discovery.relativeTime and typeof intl.relativeTime, and optionally emit a
debug/processLogger.debug call when both are present to make the behavior
explicit for maintainers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67f25fd1-1703-40cd-b986-97ac6fb00d59

📥 Commits

Reviewing files that changed from the base of the PR and between fdbfe9f and 3dbeb15.

📒 Files selected for processing (17)
  • packages/tempo/CHANGELOG.md
  • packages/tempo/doc/tempo.config.md
  • packages/tempo/doc/tempo.parse.md
  • packages/tempo/plan/licensing_architecture.md
  • packages/tempo/src/engine/engine.alias.ts
  • packages/tempo/src/engine/engine.composer.ts
  • packages/tempo/src/engine/engine.lexer.ts
  • packages/tempo/src/engine/engine.normalizer.ts
  • packages/tempo/src/module/module.parse.ts
  • packages/tempo/src/support/support.default.ts
  • packages/tempo/src/support/support.init.ts
  • packages/tempo/src/support/support.util.ts
  • packages/tempo/src/tempo.class.ts
  • packages/tempo/test/engine/numeric_resolution.test.ts
  • packages/tempo/test/engine/timestamp_config.test.ts
  • packages/tempo/test/issues/24-hour-overflow.test.ts
  • packages/tempo/test/issues/issue-fixes.test.ts
✅ Files skipped from review due to trivial changes (7)
  • packages/tempo/plan/licensing_architecture.md
  • packages/tempo/doc/tempo.config.md
  • packages/tempo/test/engine/timestamp_config.test.ts
  • packages/tempo/doc/tempo.parse.md
  • packages/tempo/test/issues/24-hour-overflow.test.ts
  • packages/tempo/CHANGELOG.md
  • packages/tempo/test/engine/numeric_resolution.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/tempo/src/support/support.init.ts
  • packages/tempo/src/support/support.default.ts
  • packages/tempo/src/engine/engine.normalizer.ts

Comment thread packages/tempo/src/engine/engine.composer.ts Outdated
Comment thread packages/tempo/src/engine/engine.composer.ts
Comment thread packages/tempo/src/engine/engine.lexer.ts Outdated
Comment thread packages/tempo/src/module/module.parse.ts
Comment thread packages/tempo/src/module/module.parse.ts
@magmacomputing
Copy link
Copy Markdown
Owner Author

@coderabbitai please review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

@magmacomputing Sure! I'll start reviewing the PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/tempo/src/support/support.init.ts (1)

245-252: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Invalid timeStamp values are still persisted after logging.

Validation now logs an error, but setProperty(state.config, optKey, unit ?? arg.value) runs unconditionally:

  • For an invalid string (e.g. 'mss'), the bad value is still written to state.config.timeStamp. Downstream UNIT_LOOKUP[unit] ?? UNIT_LOOKUP.ms masks this, but state.config.get('timeStamp') will return the invalid token.
  • For an object form without a .unit property, unit is undefined, so validation is skipped and the entire object is written to state.config.timeStamp.
  • logError(null, …) is inconsistent with the rest of this file (e.g. lines 150, 154 use logError(state.config, …)).
🛡️ Suggested fix
 			case 'timeStamp': {
-				const unit = isString(arg.value) ? arg.value : arg.value?.unit;
-				if (unit && !['ss', 'ms', 'us', 'ns'].includes(unit))
-					logError(null, `Invalid timeStamp unit: ${unit}. Expected 'ss', 'ms', 'us', or 'ns'.`);
-
-				setProperty(state.config, optKey, unit ?? arg.value);
+				const unit = isString(arg.value) ? arg.value : arg.value?.unit;
+				if (!unit || !['ss', 'ms', 'us', 'ns'].includes(unit)) {
+					logError(state.config, `[Tempo#extend] Invalid timeStamp unit: ${String(unit ?? arg.value)}. Expected 'ss', 'ms', 'us', or 'ns'.`);
+					break;
+				}
+				setProperty(state.config, optKey, unit);
 				break;
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/support/support.init.ts` around lines 245 - 252, The
timeStamp branch currently logs errors but still persists invalid values; change
the flow in the 'timeStamp' case so you only call setProperty(state.config,
optKey, ...) when the value is valid: compute unit from arg.value (string or
arg.value.unit), if unit is a string and one of ['ss','ms','us','ns'] then
setProperty with that unit; if arg.value is an object require a defined .unit
that is valid before writing the whole object; otherwise do not persist the
invalid value and call logError(state.config, ...) instead of logError(null,
...) to match the file's logging convention and avoid storing bad tokens that
UNIT_LOOKUP masking would hide.
🧹 Nitpick comments (1)
packages/tempo/src/engine/engine.composer.ts (1)

96-100: 💤 Low value

Redundant NaN check.

Number.isFinite(NaN) returns false, so !Number.isFinite(value) already covers NaN along with ±Infinity. The Number.isNaN(value) clause can be dropped.

♻️ Proposed simplification
-				if (type === 'Number' && (Number.isNaN(value) || !Number.isFinite(value))) {
+				if (type === 'Number' && !Number.isFinite(value)) {
 					logError(config, `Invalid Tempo number: ${value}`);
 					temporal = today;
 					break;
 				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.composer.ts` around lines 96 - 100, The
check for NaN is redundant: in the block where you validate numbers (the
condition checking type === 'Number'), remove the explicit Number.isNaN(value)
check and rely solely on !Number.isFinite(value) to catch NaN and ±Infinity;
update the conditional that calls logError(config, `Invalid Tempo number:
${value}`) and sets temporal = today so it only tests !Number.isFinite(value)
before logging and assigning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tempo/src/engine/engine.lexer.ts`:
- Around line 51-63: The prefix() implementation currently truncates to three
characters which breaks prefix matching for short inputs (e.g., "Tu", "Se") and
causes num()/parseWeekday() to fail; update prefix(str) to perform
case-insensitive prefix matching against the known sets (t.WEEKDAYS and t.MONTHS
or the module’s weekday/month arrays) instead of slicing to 3 chars: normalize
the input, iterate the weekday and month lists using startsWith to find the
first matching full name, return that full name (or the original string if no
match), and keep the initial isString() guard; reference function prefix and
callers like num() and parseWeekday when making the change.

---

Duplicate comments:
In `@packages/tempo/src/support/support.init.ts`:
- Around line 245-252: The timeStamp branch currently logs errors but still
persists invalid values; change the flow in the 'timeStamp' case so you only
call setProperty(state.config, optKey, ...) when the value is valid: compute
unit from arg.value (string or arg.value.unit), if unit is a string and one of
['ss','ms','us','ns'] then setProperty with that unit; if arg.value is an object
require a defined .unit that is valid before writing the whole object; otherwise
do not persist the invalid value and call logError(state.config, ...) instead of
logError(null, ...) to match the file's logging convention and avoid storing bad
tokens that UNIT_LOOKUP masking would hide.

---

Nitpick comments:
In `@packages/tempo/src/engine/engine.composer.ts`:
- Around line 96-100: The check for NaN is redundant: in the block where you
validate numbers (the condition checking type === 'Number'), remove the explicit
Number.isNaN(value) check and rely solely on !Number.isFinite(value) to catch
NaN and ±Infinity; update the conditional that calls logError(config, `Invalid
Tempo number: ${value}`) and sets temporal = today so it only tests
!Number.isFinite(value) before logging and assigning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f8edffc-dd84-4b97-9eaa-0c199af2b6e8

📥 Commits

Reviewing files that changed from the base of the PR and between 3dbeb15 and 12b76d4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json
  • packages/tempo/README.md
  • packages/tempo/doc/migration-guide.md
  • packages/tempo/package.json
  • packages/tempo/src/engine/engine.composer.ts
  • packages/tempo/src/engine/engine.lexer.ts
  • packages/tempo/src/module/module.parse.ts
  • packages/tempo/src/support/support.init.ts
  • packages/tempo/src/tempo.class.ts
  • packages/tempo/src/tempo.type.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/tempo/README.md
  • packages/tempo/doc/migration-guide.md
  • package.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/tempo/src/tempo.class.ts
  • packages/tempo/src/tempo.type.ts
  • packages/tempo/package.json
  • packages/tempo/src/module/module.parse.ts

Comment on lines +51 to 63
/** conform weekday names using prefix matching */
export function prefix(str: t.WEEKDAY | t.WEEKDAYS): t.WEEKDAY;
/** conform month names using prefix matching */
export function prefix(str: t.MONTH | t.MONTHS): t.MONTH;
/** conform any string to a weekday/month prefix if possible */
export function prefix(str: string): t.WEEKDAY | t.MONTH | string;
/** implementation */
export function prefix(str: any): any {
if (!isString(str)) return str;
const low = str.trim().toLowerCase();
if (low === '') return str;

// search in weekdays and months
for (const dict of [enums.WEEKDAY, enums.WEEKDAYS, enums.MONTH, enums.MONTHS]) {
const found = Object.keys(dict).find((key: string) => (key as string).toLowerCase().startsWith(low));
if (found) return found as T;
}

return str;
const val = str.trim();
return val.charAt(0).toUpperCase() + val.slice(1, 3).toLowerCase();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore actual prefix matching in prefix().

Line 62 is now 3-character truncation, not prefix matching. Short inputs like "Tu" or "Se" no longer resolve, so num() and parseWeekday() lose abbreviated month/weekday support.

💡 Minimal fix
 /** implementation */
 export function prefix(str: any): any {
 	if (!isString(str)) return str;
 
-	const val = str.trim();
-	return val.charAt(0).toUpperCase() + val.slice(1, 3).toLowerCase();
+	const low = str.trim().toLowerCase();
+	for (const table of [enums.WEEKDAY, enums.MONTH]) {
+		const match = ownKeys(table).find(key => {
+			const normalized = key.toLowerCase();
+			return normalized.startsWith(low) || low.startsWith(normalized);
+		});
+		if (match) return match;
+	}
+	return str;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/tempo/src/engine/engine.lexer.ts` around lines 51 - 63, The prefix()
implementation currently truncates to three characters which breaks prefix
matching for short inputs (e.g., "Tu", "Se") and causes num()/parseWeekday() to
fail; update prefix(str) to perform case-insensitive prefix matching against the
known sets (t.WEEKDAYS and t.MONTHS or the module’s weekday/month arrays)
instead of slicing to 3 chars: normalize the input, iterate the weekday and
month lists using startsWith to find the first matching full name, return that
full name (or the original string if no match), and keep the initial isString()
guard; reference function prefix and callers like num() and parseWeekday when
making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant