From 706a2b70b85db4e4519e004ce319b5089477476a Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 25 Apr 2026 13:37:34 +1000 Subject: [PATCH 1/5] sandbox document --- packages/tempo/doc/sandbox-factory.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tempo/doc/sandbox-factory.md b/packages/tempo/doc/sandbox-factory.md index 612674e..9d0c055 100644 --- a/packages/tempo/doc/sandbox-factory.md +++ b/packages/tempo/doc/sandbox-factory.md @@ -9,14 +9,14 @@ Historically, `Tempo.init()` modified the global library state. This meant that: 3. Testing multiple configurations required careful cleanup between tests. ## The Solution -`Tempo.init()` now returns a **derived class** when provided with configuration options. Each derived class maintains its own isolated `State` and `Registry`. +`Tempo.create()` returns a **derived sandboxed class**. You may optionally pass configuration options to seed that sandbox. Each derived class maintains its own isolated `State` and `Registry`. ### Example: Creating a Sandbox ```typescript import { Tempo } from '@magmacomputing/tempo'; // Create a specialized Sandbox for a Financial app -const FinTempo = Tempo.init({ +const FinTempo = Tempo.create({ period: { 'market-open': '09:30', 'market-close': '16:00' @@ -34,7 +34,7 @@ When using sandboxes, it's important to know which configuration resolved an inp ### Hierarchy of Resolution When a conflict occurs (e.g., you redefine "noon"), Tempo uses a **"Last One Wins"** strategy: 1. **Local (Instance)**: Options passed to `new Tempo(val, options)`. -2. **Sandbox (Factory)**: Options passed to `Tempo.init(options)`. +2. **Sandbox (Factory)**: Options passed to `Tempo.create(options)`. 3. **Plugins**: Aliases registered via `Tempo.extend()`. 4. **Global Defaults**: Built-in aliases like "xmas", "midnight", etc. @@ -58,7 +58,7 @@ console.log(t.parse.result); ``` ## Immutability & Security -Sandboxed classes created via `Tempo.init()` are protected by the same `@Immutable` and `@Serializable` decorators as the base class. +Sandboxed classes created via `Tempo.create()` are protected by the same `@Immutable` and `@Serializable` decorators as the base class. - The Sandbox class itself is hardened against static member modification. - Instances of the Sandbox are frozen upon construction. - The internal state is stored in a `WeakMap`, inaccessible to external code. From f90d471cddd16dbe3a6eb57313e8abc319400c7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:41:58 +0000 Subject: [PATCH 2/5] Merge origin/main into release-b-layout-order-planner, resolve conflicts in sandbox-factory.md Agent-Logs-Url: https://github.com/magmacomputing/magma/sessions/a94eb8fa-5557-4b7e-b6bc-d0c4f6b82900 Co-authored-by: magmacomputing <6935496+magmacomputing@users.noreply.github.com> --- packages/tempo/doc/sandbox-factory.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tempo/doc/sandbox-factory.md b/packages/tempo/doc/sandbox-factory.md index d57fbda..81b1e7b 100644 --- a/packages/tempo/doc/sandbox-factory.md +++ b/packages/tempo/doc/sandbox-factory.md @@ -9,7 +9,7 @@ Historically, `Tempo.init()` modified the global library state. This meant that: 3. Testing multiple configurations required careful cleanup between tests. ## The Solution -`Tempo.create()` returns a **derived sandboxed class**. You may optionally pass configuration options to seed that sandbox. Each derived class maintains its own isolated `State` and `Registry`. +`Tempo.create()` returns a **derived class** with its own isolated configuration, registry, and plugin state. Each sandbox inherits from the caller, but runs with independent internal state. ### Example: Creating a Sandbox ```typescript @@ -58,7 +58,7 @@ console.log(t.parse.result); ``` ## Immutability & Security -Sandboxed classes created via `Tempo.create()` are protected by the same `@Immutable` and `@Serializable` decorators as the base class. +Sandboxed classes created via `Tempo.create()` are protected by the same `@Immutable` and `@Serializable` decorators as the base class. - The Sandbox class itself is hardened against static member modification. - Instances of the Sandbox are frozen upon construction. - The internal state is stored in a `WeakMap`, inaccessible to external code. From ad603c42aa5b8258a7ecac5f997247920714318a Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 25 Apr 2026 13:46:26 +1000 Subject: [PATCH 3/5] rewording --- packages/tempo/doc/sandbox-factory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tempo/doc/sandbox-factory.md b/packages/tempo/doc/sandbox-factory.md index 81b1e7b..967d49e 100644 --- a/packages/tempo/doc/sandbox-factory.md +++ b/packages/tempo/doc/sandbox-factory.md @@ -9,7 +9,7 @@ Historically, `Tempo.init()` modified the global library state. This meant that: 3. Testing multiple configurations required careful cleanup between tests. ## The Solution -`Tempo.create()` returns a **derived class** with its own isolated configuration, registry, and plugin state. Each sandbox inherits from the caller, but runs with independent internal state. +`Tempo.create()` returns a **derived sandboxed class** with its own isolated configuration, registry, and plugin state. Each sandbox inherits from the caller, but runs with independent internal state. ### Example: Creating a Sandbox ```typescript From 5af63865fb477f8eaa762957830de9d832f1d241 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 25 Apr 2026 18:48:35 +1000 Subject: [PATCH 4/5] Release B --- package.json | 2 +- packages/library/package.json | 2 +- .../library/src/common/temporal.library.ts | 14 +++ packages/tempo/doc/architecture.md | 23 +++-- packages/tempo/doc/migration-guide.md | 8 ++ packages/tempo/doc/releases/v2.x.md | 5 + packages/tempo/doc/tempo.config.md | 48 ++++++++-- packages/tempo/doc/tempo.modularity.md | 2 +- packages/tempo/doc/tempo.plugin.md | 12 +-- packages/tempo/package.json | 4 +- .../tempo/plan/slick-syntax-duration-keys.md | 92 +++++++++++++++++++ packages/tempo/src/discrete/discrete.parse.ts | 11 ++- packages/tempo/src/engine/engine.layout.ts | 24 ++++- packages/tempo/src/plugin/term/term.season.ts | 6 +- packages/tempo/src/support/tempo.default.ts | 1 + packages/tempo/src/support/tempo.enum.ts | 4 +- packages/tempo/src/support/tempo.init.ts | 10 +- packages/tempo/src/tempo.class.ts | 19 +++- packages/tempo/src/tempo.type.ts | 4 +- packages/tempo/test/engine.layout.test.ts | 24 +++++ packages/tempo/test/layout.order.test.ts | 8 ++ packages/tempo/test/sandbox-factory.test.ts | 39 +++++--- packages/tempo/test/season_metadata.test.ts | 4 +- 23 files changed, 307 insertions(+), 59 deletions(-) create mode 100644 packages/tempo/plan/slick-syntax-duration-keys.md diff --git a/package.json b/package.json index 7a355b2..063c0a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tempo-monorepo", - "version": "2.5.0", + "version": "2.6.0", "private": true, "description": "Magma Computing Monorepo", "repository": { diff --git a/packages/library/package.json b/packages/library/package.json index 6ea2874..fe1ee7d 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -1,6 +1,6 @@ { "name": "@magmacomputing/library", - "version": "2.5.0", + "version": "2.6.0", "description": "Shared utility library for Tempo", "author": "Magma Computing Solutions", "license": "MIT", diff --git a/packages/library/src/common/temporal.library.ts b/packages/library/src/common/temporal.library.ts index 507fdb1..08c0b72 100644 --- a/packages/library/src/common/temporal.library.ts +++ b/packages/library/src/common/temporal.library.ts @@ -118,3 +118,17 @@ export function getTemporalIds(tz: any, cal: any): [string, string] { return [tzId || 'UTC', calId || 'iso8601']; } + +/** + * ## normalizeUtcOffset + * Convert informal UTC offset strings into the `ยฑHH:MM` format required by Temporal. + * Accepts forms like `'UTC+8'`, `'UTC-9'`, `'UTC+08:00'`, `'UTC-05:30'`. + * Returns the input unchanged if it does not match the UTCยฑ pattern. + */ +export function normalizeUtcOffset(zone: string): string { + const match = /^UTC([+-])(\d{1,2})(?::(\d{2}))?$/i.exec(zone); + if (!match) return zone; + + const [, sign, hours, minutes] = match; + return `${sign}${hours.padStart(2,'0')}:${minutes ?? '00'}`; +} diff --git a/packages/tempo/doc/architecture.md b/packages/tempo/doc/architecture.md index 20f2550..a3ca784 100644 --- a/packages/tempo/doc/architecture.md +++ b/packages/tempo/doc/architecture.md @@ -66,22 +66,21 @@ Together, these ensure that `new Tempo()` maintains an $O(1)$ constructor execut --- -## ๐Ÿ” Iteration & Enumerability (The Shadowing Chain) +## ๐Ÿ” Iteration & Enumerability (Delegator Proxies) -When using prototype shadowing, the JavaScript behavior for property inspection changes significantly. This is a trade-off for the performance gains. +Tempo now uses delegator Proxies for `fmt` and `term`, not prototype shadow-chain relocation. Enumeration behavior is therefore more standard and predictable than in earlier releases of Tempo. -### โš ๏ธ The `Object.keys()` Warning -`Object.keys(instance.fmt)` only returns the **enumerable own properties** of the current link in the shadowing chain. -- **Initially**: Returns `[]` (all evaluated getters are non-enumerable on the base). -- **After 1st Access** (e.g., `.date`): Returns `['date']`. -- **After 2nd Access** (e.g., `.time`): Returns `['time']`. The `.date` property is now located on the **immediate prototype** of the current object. +### โœ… `Object.keys()` Behavior +`Object.keys(instance.fmt)` and `Object.keys(instance.term)` return the enumerable own keys currently registered on each delegator target. -### ๐Ÿ›ก๏ธ The Flattening Iterator -Tempo implements a **Flattening Iterator** via `[Symbol.iterator]` which enables iterable consumers like `for...of`, array spread (`[...instance]`), and `Object.fromEntries(instance)` to traverse the shadowing chain (using `Object.getPrototypeOf`) and collect evaluated property entries. +- **Discovery on enumeration**: Calling `Object.keys(...)` triggers proxy discovery, which pre-registers available keys as enumerable lazy getters. +- **Before direct access**: Keys can already be visible and probeable. +- **After access**: Access memoizes values on the same target object; keys remain stable and do not "move" across prototype links. -- **`[Symbol.iterator]`**: Traverses the shadowing chain to provide a flattened view of all computed state. -- **โš ๏ธ Important**: `for...in` and object spread (`{...instance}`) **do not** use the iterator; instead, they rely on enumerable own/inherited properties and are not supported by the flattening logic. -- **`Tempo.formats` & `Tempo.terms`**: These static getters continue to provide a registry-wide view of **available** keys across the entire system, regardless of their evaluation state. +### ๐Ÿ›ก๏ธ Iteration Notes +- **`Object.keys` / `for...in` / object spread**: Operate on enumerable keys exposed by the delegator target after discovery. +- **`[Symbol.iterator]`**: Still provides explicit iterator semantics where implemented. +- **`Tempo.formats` & `Tempo.terms`**: These static getters continue to provide a registry-wide view of available keys across the system, independent of per-instance memoization state. --- diff --git a/packages/tempo/doc/migration-guide.md b/packages/tempo/doc/migration-guide.md index 79f6335..67bd443 100644 --- a/packages/tempo/doc/migration-guide.md +++ b/packages/tempo/doc/migration-guide.md @@ -86,5 +86,13 @@ As Tempo grows, it has become much more efficient for our developers to logicall 2. Replace any older internal paths with the current package subpath entries (for example, `@magmacomputing/tempo/duration`, `@magmacomputing/tempo/mutate`, `@magmacomputing/tempo/parse`, and `@magmacomputing/tempo/format`). 3. Do not pin imports in your code directly to internal folder layouts in `dist/`, since those paths may change as modules are reorganized. Instead rely wholly on your import maps. +## ๐Ÿ” Migrating to version 2.6.0 + +Season term scope output has been simplified. + +**Action Required**: +1. If you previously relied on the Chinese-specific object attached to `term.season` scope output, remove that dependency. +2. Resolve Chinese season context by creating a dedicated `Tempo` instance with the appropriate Chinese `timeZone` (or explicit `sphere`) for the interpretation you need. + ## ๐Ÿงช Testing and Stability v2.x has been hardened with a 100% pass rate on our regression suite. If you were relying on undocumented "quirks" or bugs in v1.x parsing, you may find that v2.x is more strict and deterministic. diff --git a/packages/tempo/doc/releases/v2.x.md b/packages/tempo/doc/releases/v2.x.md index cdee182..bf25cbb 100644 --- a/packages/tempo/doc/releases/v2.x.md +++ b/packages/tempo/doc/releases/v2.x.md @@ -1,4 +1,9 @@ # ๐Ÿ“œ Version 2.x History + +## [v2.6.0] - 2026-04-25 +### โš ๏ธ Migration Notes +- **Season Scope Simplification**: Removed the Chinese-specific object previously attached to all `term.season` scope's output. For Chinese season interpretation, create a dedicated `Tempo` instance with `{timeZone: 'Asia/Shanghai'}` or `{timeZone: '+08:00'}}. Note that Chinese zodiac will still be supported on `term.zodiac.CN` as before. + ## [v2.5.0] -2026-04-25 ### New Features diff --git a/packages/tempo/doc/tempo.config.md b/packages/tempo/doc/tempo.config.md index bb127bb..88f8524 100644 --- a/packages/tempo/doc/tempo.config.md +++ b/packages/tempo/doc/tempo.config.md @@ -43,22 +43,54 @@ Tempo.init({ store: 'userSettings' }); ## 2. Global Discovery -To facilitate configuration in micro-frontend architectures or when using a `