GMT Temporal Code Review
Follow these guidelines when reviewing code for GMT Temporal projects.
Review Checklist
Core Principles (Critical)
String-Only Inputs/Outputs
- All functions MUST accept/return ISO 8601 strings (e.g.,
"2024-03-10", "2024-03-10T12:00:00+01:00[Europe/Paris]")
- NO
Date objects, new Date(), or Date.now() anywhere in the codebase
- Zod schemas must validate all public API inputs
Temporal-Only
- Use ONLY
@js-temporal/polyfill - no Date imports or usage
- ESLint/Biome rules block
Date imports
Plain/Zoned Separation
- Never mix
PlainDateTime and ZonedDateTime in the same function/module
- Maintain strict separation between
plain/ and zoned/ directories
Identifying Problems
- Temporal errors: Missing try-catch around
.from(), .add(), .subtract(), .since(), .until() - these throw RangeError on invalid input
- Error handling: Functions returning
string return "" on invalid input, number returns null, boolean returns false
- Timezone bugs: Mixing plain and zoned types, incorrect timezone handling
- Test gaps: Missing locale matrix coverage, missing error path tests, missing edge cases
Design Assessment
- Plain/zoned separation maintained in new code
- Functions follow the string-in, string-out pattern
- No direct
Date usage anywhere
- Error handling follows type-safe sentinel pattern
Test Coverage
Every PR must have:
- Tests using `it.each`` (template literal syntax) for iterative cases
- Mock functions from
@gmt/test/mocks for error path testing (e.g., mockTemporalPlainDateFromThrow())
- Full locale matrix coverage for locale-aware APIs (en-US, en-GB, de-DE, fr-FR, es-ES, it-IT, pt-PT, sv-SE, is-IS, zh-CN, zh-TW, ja-JP, ko-KR, ar-SA, he-IL, ru-RU, tr-TR)
- Edge case tests (leap years, DST transitions, invalid inputs)
Long-Term Impact
Flag for senior review when changes involve:
- New Temporal API adoption patterns
- Cross-plain/zoned type mixing
- Public API signature changes
- New locale support requirements
Feedback Guidelines
Tone
- Be polite and empathetic
- Provide actionable suggestions, not vague criticism
- Phrase as questions when uncertain: "Have you considered...?"
Approval
- Approve when only minor issues remain
- Don't block PRs for stylistic preferences
- Goal is risk reduction, not perfect code
Common Patterns to Flag
Temporal Error Handling
// ❌ Bad: No try-catch
export const addDays = (dateStr: string, days: number): string => {
const date = Temporal.PlainDate.from(dateStr); // Can throw!
return date.add({ days }).toString();
};
// ✅ Good: Wrapped in try-catch
export const addDays = (dateStr: string, days: number): string => {
try {
const date = Temporal.PlainDate.from(dateStr);
return date.add({ days }).toString();
} catch {
return "";
}
};
Date Object Usage
// ❌ Bad: Date object usage
export const getNow = (): string => {
return new Date().toISOString();
};
// ✅ Good: Temporal-only
export const getNow = (): string => {
return Temporal.Now.instant().toString();
};
Plain/Zoned Mixing
// ❌ Bad: Mixing plain and zoned
export const badFunction = (plainDate: string, zonedDateTime: string): string => {
const p = Temporal.PlainDate.from(plainDate);
const z = Temporal.ZonedDateTime.from(zonedDateTime);
// Logic mixing these is error-prone
};
// ✅ Good: Separate concerns
export const goodFunction = (zonedDateTime: string): string => {
try {
const z = Temporal.ZonedDateTime.from(zonedDateTime);
return z.add({ days: 1 }).toString();
} catch {
return "";
}
};
Test Patterns
// ❌ Bad: Array syntax for it.each
it.each([
["2024-03-10", 10],
["2024-03-15", 15],
])("returns $expected for $input", (input, expected) => {
expect(getDay(input)).toBe(expected);
});
// ✅ Good: Template literal syntax
it.each`
input | expected
${"2024-03-10"} | ${10}
${"2024-03-15"} | ${15}
`("returns $expected for $input", ({ input, expected }) => {
expect(getDay(input)).toBe(expected);
});
Error Path Testing
// ✅ Use pre-built mocks from @gmt/test/mocks
import { mockTemporalPlainDateFromThrow } from "@gmt/test/mocks";
it("returns empty string when Temporal.PlainDate.from throws", () => {
mockTemporalPlainDateFromThrow();
const result = addDays("2024-03-10", 1);
expect(result).toBe("");
});
References
Source: burglekitt/gmt — distributed by TomeVault.
1---2name: code-review-253description: Perform code reviews for GMT Temporal projects (gmt, gmt-oxlint, gmt-eslint, gmt-biome). Focus on Temporal-specific patterns, string-only I/O, error handling, and test coverage. Use when this capability is needed.4---56# GMT Temporal Code Review78Follow these guidelines when reviewing code for GMT Temporal projects.910## Review Checklist1112### Core Principles (Critical)13141. **String-Only Inputs/Outputs**15 - All functions MUST accept/return ISO 8601 strings (e.g., `"2024-03-10"`, `"2024-03-10T12:00:00+01:00[Europe/Paris]"`)16 - NO `Date` objects, `new Date()`, or `Date.now()` anywhere in the codebase17 - Zod schemas must validate all public API inputs18192. **Temporal-Only**20 - Use ONLY `@js-temporal/polyfill` - no `Date` imports or usage21 - ESLint/Biome rules block `Date` imports22233. **Plain/Zoned Separation**24 - Never mix `PlainDateTime` and `ZonedDateTime` in the same function/module25 - Maintain strict separation between `plain/` and `zoned/` directories2627### Identifying Problems2829- **Temporal errors**: Missing try-catch around `.from()`, `.add()`, `.subtract()`, `.since()`, `.until()` - these throw `RangeError` on invalid input30- **Error handling**: Functions returning `string` return `""` on invalid input, `number` returns `null`, `boolean` returns `false`31- **Timezone bugs**: Mixing plain and zoned types, incorrect timezone handling32- **Test gaps**: Missing locale matrix coverage, missing error path tests, missing edge cases3334### Design Assessment3536- Plain/zoned separation maintained in new code37- Functions follow the string-in, string-out pattern38- No direct `Date` usage anywhere39- Error handling follows type-safe sentinel pattern4041### Test Coverage4243Every PR must have:4445- Tests using `it.each`` (template literal syntax) for iterative cases46- Mock functions from `@gmt/test/mocks` for error path testing (e.g., `mockTemporalPlainDateFromThrow()`)47- Full locale matrix coverage for locale-aware APIs (en-US, en-GB, de-DE, fr-FR, es-ES, it-IT, pt-PT, sv-SE, is-IS, zh-CN, zh-TW, ja-JP, ko-KR, ar-SA, he-IL, ru-RU, tr-TR)48- Edge case tests (leap years, DST transitions, invalid inputs)4950### Long-Term Impact5152Flag for senior review when changes involve:53- New Temporal API adoption patterns54- Cross-plain/zoned type mixing55- Public API signature changes56- New locale support requirements5758## Feedback Guidelines5960### Tone6162- Be polite and empathetic63- Provide actionable suggestions, not vague criticism64- Phrase as questions when uncertain: "Have you considered...?"6566### Approval6768- Approve when only minor issues remain69- Don't block PRs for stylistic preferences70- Goal is risk reduction, not perfect code7172## Common Patterns to Flag7374### Temporal Error Handling7576```typescript77// ❌ Bad: No try-catch78export const addDays = (dateStr: string, days: number): string => {79 const date = Temporal.PlainDate.from(dateStr); // Can throw!80 return date.add({ days }).toString();81};8283// ✅ Good: Wrapped in try-catch84export const addDays = (dateStr: string, days: number): string => {85 try {86 const date = Temporal.PlainDate.from(dateStr);87 return date.add({ days }).toString();88 } catch {89 return "";90 }91};92```9394### Date Object Usage9596```typescript97// ❌ Bad: Date object usage98export const getNow = (): string => {99 return new Date().toISOString();100};101102// ✅ Good: Temporal-only103export const getNow = (): string => {104 return Temporal.Now.instant().toString();105};106```107108### Plain/Zoned Mixing109110```typescript111// ❌ Bad: Mixing plain and zoned112export const badFunction = (plainDate: string, zonedDateTime: string): string => {113 const p = Temporal.PlainDate.from(plainDate);114 const z = Temporal.ZonedDateTime.from(zonedDateTime);115 // Logic mixing these is error-prone116};117118// ✅ Good: Separate concerns119export const goodFunction = (zonedDateTime: string): string => {120 try {121 const z = Temporal.ZonedDateTime.from(zonedDateTime);122 return z.add({ days: 1 }).toString();123 } catch {124 return "";125 }126};127```128129### Test Patterns130131```typescript132// ❌ Bad: Array syntax for it.each133it.each([134 ["2024-03-10", 10],135 ["2024-03-15", 15],136])("returns $expected for $input", (input, expected) => {137 expect(getDay(input)).toBe(expected);138});139140// ✅ Good: Template literal syntax141it.each`142 input | expected143 ${"2024-03-10"} | ${10}144 ${"2024-03-15"} | ${15}145`("returns $expected for $input", ({ input, expected }) => {146 expect(getDay(input)).toBe(expected);147});148```149150### Error Path Testing151152```typescript153// ✅ Use pre-built mocks from @gmt/test/mocks154import { mockTemporalPlainDateFromThrow } from "@gmt/test/mocks";155156it("returns empty string when Temporal.PlainDate.from throws", () => {157 mockTemporalPlainDateFromThrow();158 const result = addDays("2024-03-10", 1);159 expect(result).toBe("");160});161```162163## References164165- [GMT Temporal Agent Rules](../AGENTS.md)166167---168> Source: [burglekitt/gmt](https://github.com/burglekitt/gmt) — distributed by [TomeVault](https://tomevault.io).169<!-- tomevault:4.0:skill_md:2026-05-22 -->