Skip to content

fix(wren-ui): normalize Instruction timestamp serialization to fix #2073#2165

Open
KerberosClaw wants to merge 2 commits intoCanner:mainfrom
KerberosClaw:fix/instruction-timestamp-serialization
Open

fix(wren-ui): normalize Instruction timestamp serialization to fix #2073#2165
KerberosClaw wants to merge 2 commits intoCanner:mainfrom
KerberosClaw:fix/instruction-timestamp-serialization

Conversation

@KerberosClaw
Copy link
Copy Markdown

@KerberosClaw KerberosClaw commented Mar 21, 2026

Summary

  • Fixes Timestamp parsing issues on knowledge/instructions #2073 — Instruction createdAt / updatedAt displays incorrect historical dates (e.g., 1770-11-08) on the Knowledge → Instructions page when using PostgreSQL
  • Root cause: instructionResolver was missing a nested resolver for timestamp fields. When PostgreSQL returns Date objects, graphql-js serializes them via Date.valueOf() → epoch milliseconds string, which dayjs misparses as a historical year
  • Adds getInstructionNestedResolver() to InstructionResolver, following the same pattern used by SqlPairResolver and ApiHistoryResolver

Test plan

  • Verified with graphql@16.8.1 + dayjs@1.11.13 in Docker:
    • PG Date object → without fix: dayjs("1764816830199")1770-11-08 (WRONG)
    • PG Date object → with fix: dayjs("2025-12-04T02:53:50.199Z")2025-12-04 (CORRECT)
    • SQLite string → with fix: no behavioral change (string passthrough)
    • SQLite CURRENT_TIMESTAMP format → with fix: correctly normalized to ISO
    • null → returns null (safe)
  • Confirmed existing SqlPairResolver and ApiHistoryResolver use identical pattern
  • Confirmed original code also fails yarn build (pre-existing vega-lite type error on main), so no regression introduced

Summary by CodeRabbit

  • New Features
    • Instruction createdAt and updatedAt fields are now returned as ISO-8601 strings for consistent date handling across the platform.
    • The Instruction type’s nested date fields are now available in API responses, ensuring clients receive properly formatted timestamps.

)

Add nested resolver for Instruction type to convert createdAt/updatedAt
to ISO string, matching the pattern used by SqlPair and ApiHistoryResponse.

Without this, PostgreSQL returns Date objects that graphql-js serializes
via Date.valueOf() into epoch milliseconds strings, which dayjs misparses
as historical dates (e.g., 1770-11-08 instead of 2025-12-04).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 45ef1708-2cec-43f6-acc0-f3837bef387f

📥 Commits

Reviewing files that changed from the base of the PR and between 8e97c53 and dffa493.

📒 Files selected for processing (1)
  • wren-ui/src/apollo/server/resolvers/instructionResolver.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren-ui/src/apollo/server/resolvers/instructionResolver.ts

Walkthrough

Added a GraphQL nested resolver for the Instruction type that exposes createdAt and updatedAt field resolvers which convert stored timestamp values into ISO 8601 date strings.

Changes

Cohort / File(s) Summary
GraphQL Instruction Resolver
wren-ui/src/apollo/server/resolvers.ts, wren-ui/src/apollo/server/resolvers/instructionResolver.ts
Added getInstructionNestedResolver() on InstructionResolver and wired it under Instruction in the exported resolvers. The nested resolver provides createdAt and updatedAt field resolvers that convert timestamp values to ISO 8601 strings (returns null when source value is absent).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 Hop, hop — the timestamps now line up right,
No more odd years in the dead of night.
Numbers turned to ISO, tidy and clean,
The calendar smiles, all neat and serene!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(wren-ui): normalize Instruction timestamp serialization to fix #2073' accurately reflects the main change: adding timestamp normalization for Instruction fields to resolve issue #2073.
Linked Issues check ✅ Passed The PR fully addresses issue #2073 by adding nested resolvers that normalize Instruction timestamps to ISO strings, preventing dayjs from misinterpreting numeric epoch-millisecond strings as historical dates.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the timestamp serialization issue: adding getInstructionNestedResolver() to InstructionResolver and wiring it in resolvers.ts, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wren-ui/src/apollo/server/resolvers/instructionResolver.ts`:
- Around line 95-103: The createdAt and updatedAt field resolvers in
instructionResolver.ts return null for falsy timestamps which violates the
schema's non-nullable String!; update the createdAt and updatedAt resolver
functions (the createdAt: (instruction: Instruction) => { ... } and updatedAt:
(instruction: Instruction) => { ... } implementations) to always return a valid
ISO string — e.g. if instruction.createdAt or instruction.updatedAt is falsy,
return new Date().toISOString() (or another deterministic fallback ISO string)
instead of null so the resolvers never return null for the non-nullable GraphQL
fields.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9429a695-b85e-406f-8df8-b9179be40238

📥 Commits

Reviewing files that changed from the base of the PR and between eea642f and 8e97c53.

📒 Files selected for processing (2)
  • wren-ui/src/apollo/server/resolvers.ts
  • wren-ui/src/apollo/server/resolvers/instructionResolver.ts

Comment on lines +95 to +103
return instruction.createdAt
? new Date(instruction.createdAt).toISOString()
: null;
},
updatedAt: (instruction: Instruction) => {
return instruction.updatedAt
? new Date(instruction.updatedAt).toISOString()
: null;
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid returning null for non-nullable GraphQL timestamp fields.

Instruction.createdAt / updatedAt are non-nullable in schema (String!), but this resolver returns null on falsy values, which can cause runtime GraphQL nullability errors.

💡 Suggested fix
+  private normalizeInstructionTimestamp(value: unknown, fieldName: string): string {
+    if (value == null) {
+      throw new Error(`Instruction.${fieldName} is required`);
+    }
+
+    const date = new Date(value as string | number | Date);
+    if (Number.isNaN(date.getTime())) {
+      throw new Error(`Invalid Instruction.${fieldName}: ${String(value)}`);
+    }
+
+    return date.toISOString();
+  }
+
   public getInstructionNestedResolver = () => ({
     createdAt: (instruction: Instruction) => {
-      return instruction.createdAt
-        ? new Date(instruction.createdAt).toISOString()
-        : null;
+      return this.normalizeInstructionTimestamp(
+        instruction.createdAt,
+        'createdAt',
+      );
     },
     updatedAt: (instruction: Instruction) => {
-      return instruction.updatedAt
-        ? new Date(instruction.updatedAt).toISOString()
-        : null;
+      return this.normalizeInstructionTimestamp(
+        instruction.updatedAt,
+        'updatedAt',
+      );
     },
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return instruction.createdAt
? new Date(instruction.createdAt).toISOString()
: null;
},
updatedAt: (instruction: Instruction) => {
return instruction.updatedAt
? new Date(instruction.updatedAt).toISOString()
: null;
},
private normalizeInstructionTimestamp(value: unknown, fieldName: string): string {
if (value == null) {
throw new Error(`Instruction.${fieldName} is required`);
}
const date = new Date(value as string | number | Date);
if (Number.isNaN(date.getTime())) {
throw new Error(`Invalid Instruction.${fieldName}: ${String(value)}`);
}
return date.toISOString();
}
public getInstructionNestedResolver = () => ({
createdAt: (instruction: Instruction) => {
return this.normalizeInstructionTimestamp(
instruction.createdAt,
'createdAt',
);
},
updatedAt: (instruction: Instruction) => {
return this.normalizeInstructionTimestamp(
instruction.updatedAt,
'updatedAt',
);
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wren-ui/src/apollo/server/resolvers/instructionResolver.ts` around lines 95 -
103, The createdAt and updatedAt field resolvers in instructionResolver.ts
return null for falsy timestamps which violates the schema's non-nullable
String!; update the createdAt and updatedAt resolver functions (the createdAt:
(instruction: Instruction) => { ... } and updatedAt: (instruction: Instruction)
=> { ... } implementations) to always return a valid ISO string — e.g. if
instruction.createdAt or instruction.updatedAt is falsy, return new
Date().toISOString() (or another deterministic fallback ISO string) instead of
null so the resolvers never return null for the non-nullable GraphQL fields.

Instruction.createdAt and updatedAt are String! (non-nullable) in the
GraphQL schema, so the resolver should not return null. Follow the
SqlPairResolver pattern which also omits null checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

Timestamp parsing issues on knowledge/instructions

1 participant