fix: preserve nested hard-break content and use timestamptz
remarkSplitBlocks was splicing after-break paragraphs onto the root tree, which dropped content inside list items and other nested parents. Insert the split sibling into the actual parent instead. Also store timestamps as timestamptz so Drizzle no longer treats naive timestamp values as UTC and shifts displayed times by the server offset.
This commit is contained in:
+21
-21
@@ -10,37 +10,37 @@ import rehypeKatex from 'rehype-katex';
|
||||
function remarkSplitBlocks() {
|
||||
return (tree: any) => {
|
||||
visit(tree, 'paragraph', (node, index, parent) => {
|
||||
if (parent == null || index == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const breakIndex = node.children.findIndex((child: any) => child.type === 'break');
|
||||
|
||||
if (breakIndex !== -1) {
|
||||
const beforeBreak = node.children.slice(0, breakIndex);
|
||||
const afterBreak = node.children.slice(breakIndex + 1);
|
||||
if (breakIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.children = beforeBreak;
|
||||
const beforeBreak = node.children.slice(0, breakIndex);
|
||||
const afterBreak = node.children.slice(breakIndex + 1);
|
||||
|
||||
// Keep content before the break in the current paragraph.
|
||||
node.children = beforeBreak;
|
||||
|
||||
// Insert content after the break as the next sibling of this
|
||||
// paragraph in its actual parent (root, listItem, blockquote, etc.).
|
||||
// Previously this only spliced into tree.children and dropped
|
||||
// nested content when the parent wasn't a direct root child.
|
||||
if (afterBreak.length > 0) {
|
||||
const newParagraph = {
|
||||
type: 'paragraph',
|
||||
children: afterBreak,
|
||||
};
|
||||
|
||||
let rootChildIndex = -1;
|
||||
if (parent.type === 'root') {
|
||||
rootChildIndex = index!;
|
||||
} else {
|
||||
rootChildIndex = tree.children.findIndex((child: any) =>
|
||||
child === parent || (child.children && child.children.includes(node))
|
||||
);
|
||||
parent.children.splice(index + 1, 0, newParagraph);
|
||||
|
||||
if (rootChildIndex === -1) {
|
||||
rootChildIndex = tree.children.indexOf(parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (rootChildIndex !== -1) {
|
||||
tree.children.splice(rootChildIndex + 1, 0, newParagraph);
|
||||
}
|
||||
|
||||
return index! + 1;
|
||||
// Continue at the newly inserted paragraph so further
|
||||
// hard breaks inside it are split as well.
|
||||
return index + 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
-- Convert naive timestamps to timestamptz.
|
||||
-- Existing values were written as session-local wall clocks (via now()/defaultNow)
|
||||
-- or occasionally as UTC components (drizzle mapToDriverValue toISOString).
|
||||
-- Interpret stored values in the current session TimeZone so local wall-clock
|
||||
-- rows (the common path for messages) keep the same absolute instant.
|
||||
--
|
||||
-- Drizzle reads naive `timestamp` as UTC (textToDateWithTz), which shifted
|
||||
-- displayed times by the server offset (e.g. -5h in America/Chicago).
|
||||
|
||||
ALTER TABLE "providers"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "models"
|
||||
ALTER COLUMN "released_at" TYPE timestamptz USING "released_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "agents"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "topics"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "messages"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "message_parts"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "last_updated_at" TYPE timestamptz USING "last_updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "tool_calls"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "files"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "attachments"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "users"
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "sessions"
|
||||
ALTER COLUMN "expires_at" TYPE timestamptz USING "expires_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "accounts"
|
||||
ALTER COLUMN "access_token_expires_at" TYPE timestamptz USING "access_token_expires_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "refresh_token_expires_at" TYPE timestamptz USING "refresh_token_expires_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
|
||||
ALTER TABLE "verifications"
|
||||
ALTER COLUMN "expires_at" TYPE timestamptz USING "expires_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "created_at" TYPE timestamptz USING "created_at" AT TIME ZONE current_setting('TimeZone'),
|
||||
ALTER COLUMN "updated_at" TYPE timestamptz USING "updated_at" AT TIME ZONE current_setting('TimeZone');
|
||||
File diff suppressed because it is too large
Load Diff
+29
-24
@@ -11,6 +11,11 @@ import {
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
// Always use timestamptz. Drizzle treats naive `timestamp` values as UTC on read
|
||||
// (via textToDateWithTz), which shifts displayed times by the server offset
|
||||
// (e.g. -5h in America/Chicago).
|
||||
const timestamptz = (name: string) => timestamp(name, { withTimezone: true });
|
||||
|
||||
export const roleEnum = pgEnum('role', ['user', 'assistant']);
|
||||
export const partTypeEnum = pgEnum('part_type', ['reasoning', 'text', 'tool-call', 'file']);
|
||||
export const statusEnum = pgEnum('status', ['pending', 'completed', 'failed', 'cancelled']);
|
||||
@@ -54,8 +59,8 @@ export const providers = pgTable('providers', {
|
||||
apiKey?: string;
|
||||
apiProxyUrl?: string;
|
||||
}>().default({}),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamptz('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
}, (table) => [
|
||||
index('providers_userId_idx').on(table.userId),
|
||||
]);
|
||||
@@ -89,7 +94,7 @@ export const models = pgTable('models', {
|
||||
supportedParameters: text('supported_parameters').array(),
|
||||
isCustom: boolean('is_custom').notNull().default(false),
|
||||
enabled: boolean('enabled').notNull().default(true),
|
||||
releasedAt: timestamp('released_at'),
|
||||
releasedAt: timestamptz('released_at'),
|
||||
}, (table) => [
|
||||
index('models_providerId_idx').on(table.providerId),
|
||||
index('models_userId_idx').on(table.userId),
|
||||
@@ -109,7 +114,7 @@ export const agents = pgTable('agents', {
|
||||
maxResults: number;
|
||||
}
|
||||
}>().default({}),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('agents_userId_idx').on(table.userId),
|
||||
]);
|
||||
@@ -120,7 +125,7 @@ export const topics = pgTable('topics', {
|
||||
agentId: text('agent_id').notNull().references(() => agents.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
renaming: boolean('renaming').default(false),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('topics_agentId_idx').on(table.agentId),
|
||||
index('topics_userId_idx').on(table.userId),
|
||||
@@ -136,8 +141,8 @@ export const messages = pgTable('messages', {
|
||||
content: text('content'),
|
||||
activeChildId: text('active_child_id'),
|
||||
deleted: boolean('deleted').default(false),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamptz('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('messages_topicId_idx').on(table.topicId),
|
||||
index('messages_parentMessageId_idx').on(table.parentMessageId),
|
||||
@@ -154,8 +159,8 @@ export const messageParts = pgTable('message_parts', {
|
||||
content: text('content'),
|
||||
providerOptions: jsonb('provider_options'),
|
||||
finished: boolean('finished').notNull().default(false),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
lastUpdatedAt: timestamp('last_updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
lastUpdatedAt: timestamptz('last_updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
}, (table) => [
|
||||
index('message_parts_topicId_idx').on(table.topicId),
|
||||
index('message_parts_messageId_idx').on(table.messageId),
|
||||
@@ -179,7 +184,7 @@ export const toolCalls = pgTable('tool_calls', {
|
||||
type: ToolCallType;
|
||||
value: string;
|
||||
}>(),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('tool_calls_userId_idx').on(table.userId),
|
||||
]);
|
||||
@@ -214,7 +219,7 @@ export const files = pgTable('files', {
|
||||
mimeType: text('mime_type').notNull(),
|
||||
size: integer('size').notNull().default(0),
|
||||
url: text('url').notNull(),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('files_userId_idx').on(table.userId),
|
||||
]);
|
||||
@@ -235,7 +240,7 @@ export const attachments = pgTable('attachments', {
|
||||
topicId: text('topic_id').notNull().references(() => topics.id, { onDelete: 'cascade' }),
|
||||
messageId: text('message_id').notNull().references(() => messages.id, { onDelete: 'cascade' }),
|
||||
fileId: text('file_id').notNull().references(() => files.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
index('attachments_topicId_idx').on(table.topicId),
|
||||
index('attachments_messageId_idx').on(table.messageId),
|
||||
@@ -248,8 +253,8 @@ export const users = pgTable("users", {
|
||||
email: text("email").notNull().unique(),
|
||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
createdAt: timestamptz("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamptz("updated_at")
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
@@ -259,10 +264,10 @@ export const sessions = pgTable(
|
||||
"sessions",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
expiresAt: timestamptz("expires_at").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
createdAt: timestamptz("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamptz("updated_at")
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
ipAddress: text("ip_address"),
|
||||
@@ -286,12 +291,12 @@ export const accounts = pgTable(
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
idToken: text("id_token"),
|
||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||
accessTokenExpiresAt: timestamptz("access_token_expires_at"),
|
||||
refreshTokenExpiresAt: timestamptz("refresh_token_expires_at"),
|
||||
scope: text("scope"),
|
||||
password: text("password"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
createdAt: timestamptz("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamptz("updated_at")
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
},
|
||||
@@ -304,9 +309,9 @@ export const verifications = pgTable(
|
||||
id: text("id").primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
expiresAt: timestamptz("expires_at").notNull(),
|
||||
createdAt: timestamptz("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamptz("updated_at")
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
|
||||
Reference in New Issue
Block a user