perf(activity): page, summarize and reconcile activity history in Main by index
Schema 57 rebuilds activity_history_records with order_seq (newer = larger;
a move to the front assigns a new maximum) and stored generated columns
(record_id, occurrence, status, status_group, kind, request_id,
conversation_id, project_id, created_at) with indexes. The order is migrated
from record_order_json exactly; legacy duplicates keep their occurrence and
only the first is shown. record_order_json is no longer written.
ActivityHistoryRepository owns the SQL: page({before, limit <= 500, filter,
projectId, conversationId}) newest first by index, summary() with the counts
and conversation titles/status the Activity page derived from the full list,
reconcile() joined with tasks with a renderer-localized detail line, update()
applied directly in SQL without reading the history, replace() for the
legacy migration, clear(). New activity-history:page/summary/reconcile IPC
with zod validation; page and summary run on the readonly worker.
perf:main measures 24,000 records: startup load 106 ms on Main (14 MB) ->
first page + summary 13.5 ms on the worker (0.6 ms loop block, 114 KB).
This commit is contained in:
@@ -133,6 +133,56 @@ export const activityHistoryUpdateSchema = z
|
||||
})
|
||||
.strict()
|
||||
|
||||
/**
|
||||
* Activity history pages (PERF-15), newest first. A record ID shows once:
|
||||
* legacy duplicates after the first occurrence are left out. `before` is the
|
||||
* `nextBefore` of the previous page.
|
||||
*/
|
||||
export const activityHistoryFilterSchema = z.enum(['all', 'active', 'failed'])
|
||||
export const activityHistoryPageRequestSchema = z
|
||||
.object({
|
||||
before: z.number().int().optional(),
|
||||
limit: z.number().int().min(1).max(500),
|
||||
filter: activityHistoryFilterSchema.optional(),
|
||||
projectId: z.string().min(1).max(256).optional(),
|
||||
conversationId: z.string().min(1).max(256).optional()
|
||||
})
|
||||
.strict()
|
||||
export const activityHistorySummaryRequestSchema = z
|
||||
.object({
|
||||
/** Conversations whose title and status are needed (the loaded ones). */
|
||||
conversationIds: z.array(z.string().min(1).max(256)).max(5_000)
|
||||
})
|
||||
.strict()
|
||||
export const activityHistoryReconcileRequestSchema = z
|
||||
.object({
|
||||
activeRequestIds: z.array(z.string().min(1).max(256)).max(10_000),
|
||||
/** The localized line appended to records marked interrupted. */
|
||||
interruptedDetail: z.string().max(1_000)
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type ActivityHistoryFilter = z.infer<typeof activityHistoryFilterSchema>
|
||||
export type ActivityHistoryPageRequest = z.infer<typeof activityHistoryPageRequestSchema>
|
||||
export type ActivityHistoryPage = {
|
||||
records: ActivityRecord[]
|
||||
/** Set when older records may follow. */
|
||||
nextBefore?: number
|
||||
}
|
||||
export type ActivityHistorySummaryRequest = z.infer<typeof activityHistorySummaryRequestSchema>
|
||||
export type ActivityHistorySummary = {
|
||||
/** Record counts per filter, over the whole history. */
|
||||
counts: Record<ActivityHistoryFilter, number>
|
||||
/**
|
||||
* Per requested conversation: the title of its newest request record and
|
||||
* the conversation status (latest request's result, else latest result,
|
||||
* else latest record), over the whole history.
|
||||
*/
|
||||
conversations: Record<string, { title?: string; status: ActivityRecord['status'] }>
|
||||
legacyHistoryMayBeIncomplete: boolean
|
||||
}
|
||||
export type ActivityHistoryReconcileRequest = z.infer<typeof activityHistoryReconcileRequestSchema>
|
||||
|
||||
export type ActivityRecord = z.infer<typeof activityRecordSchema>
|
||||
export type ActivityHistorySnapshot = z.infer<
|
||||
typeof activityHistorySnapshotSchema
|
||||
|
||||
Reference in New Issue
Block a user