@tomradex/domain 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ import { type FieldDef } from "./fields.js";
2
+ import type { FieldValue, Item } from "./types.js";
3
+ /**
4
+ * How a view draws the backlog. Board and table group and sort; a graph draws
5
+ * the edges a plugin contributes. A view is one layout plus one query.
6
+ */
7
+ export type Layout = "board" | "table" | "graph";
8
+ export declare const LAYOUTS: readonly Layout[];
9
+ /**
10
+ * `in` / `not-in` take values; a null value means "empty". `contains` is a
11
+ * case-insensitive substring over text. `gte` / `lte` compare numbers and dates.
12
+ */
13
+ export type FilterOp = "in" | "not-in" | "contains" | "gte" | "lte";
14
+ export interface FilterClause {
15
+ field: string;
16
+ op: FilterOp;
17
+ values: (FieldValue | null)[];
18
+ }
19
+ export interface SortKey {
20
+ field: string;
21
+ dir: "asc" | "desc";
22
+ }
23
+ /**
24
+ * The stored shape of a view's query and the live shape the bar edits. Empty
25
+ * `columns` means every key the field has; empty `sortBy` means manual order.
26
+ */
27
+ export interface ViewQuery {
28
+ filter: FilterClause[];
29
+ /** Board layout: the field one column stands for. */
30
+ columnBy: string | null;
31
+ /** Visible column keys in order; empty shows them all. */
32
+ columns: string[];
33
+ /** Swimlanes on a board, groups in a table. */
34
+ groupBy: string | null;
35
+ sortBy: SortKey[];
36
+ /** Fields drawn on cards and as table columns; empty means the layout's default. */
37
+ fields: string[];
38
+ /** WIP cap per column key. Advisory — a drop is never blocked. */
39
+ wip: Record<string, number>;
40
+ /** Graph layout: the contributed graph whose edges are drawn. */
41
+ graph: string | null;
42
+ }
43
+ export declare const EMPTY_QUERY: ViewQuery;
44
+ /** Fill a partially stored query so every consumer sees the full shape. */
45
+ export declare const normalizeQuery: (q: Partial<ViewQuery> | null | undefined) => ViewQuery;
46
+ export interface QueryContext {
47
+ defs: Map<string, FieldDef>;
48
+ /** Position of an option in its field's order; used to sort and to order columns. */
49
+ optionRank(fieldId: string, key: string): number;
50
+ /** A display name for an option key, for sorting fields whose options have no rank (epics by title). */
51
+ optionName(fieldId: string, key: string): string;
52
+ }
53
+ /** A `YYYY-MM-DD` bound against an epoch-ms value covers the whole day it names. */
54
+ export declare function dayBound(value: FieldValue | null | undefined, op: "gte" | "lte"): number | null;
55
+ export declare function matchesClause(item: Item, clause: FilterClause): boolean;
56
+ export declare const matchesFilter: (item: Item, filter: FilterClause[]) => boolean;
57
+ /** A comparable for one sort key: numbers for numeric fields, strings otherwise, null when empty. */
58
+ export declare function sortKeyOf(item: Item, fieldId: string, ctx: QueryContext): number | string | null;
59
+ /** Empty values sort last whichever direction; ties fall through to manual rank, then recency. */
60
+ export declare function compareItems(a: Item, b: Item, sortBy: SortKey[], ctx: QueryContext, rankOf: (item: Item) => number): number;
61
+ /** The keys a groupable field can take, in the field's order, with the empty key last where it applies. */
62
+ export declare function groupSpaceOf(def: FieldDef, options: {
63
+ id: string;
64
+ }[]): string[];
65
+ //# sourceMappingURL=query.d.ts.map
package/dist/query.js ADDED
@@ -0,0 +1,99 @@
1
+ import { CHECKED, EMPTY_KEY, fieldValueOf } from "./fields.js";
2
+ export const LAYOUTS = ["board", "table", "graph"];
3
+ export const EMPTY_QUERY = { filter: [], columnBy: "status", columns: [], groupBy: null, sortBy: [], fields: [], wip: {}, graph: null };
4
+ /** Fill a partially stored query so every consumer sees the full shape. */
5
+ export const normalizeQuery = (q) => ({ ...EMPTY_QUERY, ...(q ?? {}), wip: { ...(q?.wip ?? {}) } });
6
+ const norm = (v) => (v === null ? null : String(v));
7
+ const DAY = 86_400_000;
8
+ /** A `YYYY-MM-DD` bound against an epoch-ms value covers the whole day it names. */
9
+ export function dayBound(value, op) {
10
+ if (value === null || value === undefined)
11
+ return null;
12
+ if (typeof value === "number")
13
+ return value;
14
+ const t = Date.parse(value);
15
+ if (Number.isNaN(t))
16
+ return null;
17
+ return op === "lte" ? t + DAY - 1 : t;
18
+ }
19
+ export function matchesClause(item, clause) {
20
+ const raw = fieldValueOf(item, clause.field);
21
+ const values = Array.isArray(raw) ? raw : raw === null ? [] : [raw];
22
+ switch (clause.op) {
23
+ case "in": {
24
+ const wanted = new Set(clause.values.map(norm));
25
+ if (!values.length)
26
+ return wanted.has(null);
27
+ return values.some((v) => wanted.has(String(v)));
28
+ }
29
+ case "not-in": {
30
+ const wanted = new Set(clause.values.map(norm));
31
+ if (!values.length)
32
+ return !wanted.has(null);
33
+ return !values.some((v) => wanted.has(String(v)));
34
+ }
35
+ case "contains": {
36
+ const needle = String(clause.values[0] ?? "").toLowerCase();
37
+ if (!needle)
38
+ return true;
39
+ return values.some((v) => String(v).toLowerCase().includes(needle));
40
+ }
41
+ case "gte":
42
+ case "lte": {
43
+ if (!values.length)
44
+ return false;
45
+ const v = values[0];
46
+ // A numeric field may be bounded by a date string (Updated); anything else compares like for like.
47
+ const bound = typeof v === "number" ? dayBound(clause.values[0], clause.op) : (clause.values[0] ?? null);
48
+ if (bound === null)
49
+ return false;
50
+ const cmp = typeof v === "number" && typeof bound === "number" ? v - bound : String(v).localeCompare(String(bound));
51
+ return clause.op === "gte" ? cmp >= 0 : cmp <= 0;
52
+ }
53
+ }
54
+ }
55
+ export const matchesFilter = (item, filter) => filter.every((c) => matchesClause(item, c));
56
+ /** A comparable for one sort key: numbers for numeric fields, strings otherwise, null when empty. */
57
+ export function sortKeyOf(item, fieldId, ctx) {
58
+ const raw = fieldValueOf(item, fieldId);
59
+ if (raw === null || Array.isArray(raw))
60
+ return null;
61
+ const def = ctx.defs.get(fieldId);
62
+ if (!def)
63
+ return null;
64
+ if (def.type === "select")
65
+ return ctx.optionRank(fieldId, String(raw));
66
+ if (def.type === "item")
67
+ return ctx.optionName(fieldId, String(raw)).toLowerCase();
68
+ if (def.type === "text")
69
+ return String(raw).toLowerCase();
70
+ return raw;
71
+ }
72
+ /** Empty values sort last whichever direction; ties fall through to manual rank, then recency. */
73
+ export function compareItems(a, b, sortBy, ctx, rankOf) {
74
+ for (const key of sortBy) {
75
+ const ka = sortKeyOf(a, key.field, ctx);
76
+ const kb = sortKeyOf(b, key.field, ctx);
77
+ if (ka === kb)
78
+ continue;
79
+ if (ka === null)
80
+ return 1;
81
+ if (kb === null)
82
+ return -1;
83
+ const cmp = typeof ka === "number" && typeof kb === "number" ? ka - kb : String(ka).localeCompare(String(kb));
84
+ if (cmp !== 0)
85
+ return key.dir === "desc" ? -cmp : cmp;
86
+ }
87
+ return rankOf(a) - rankOf(b) || b.updatedAt - a.updatedAt;
88
+ }
89
+ /** The keys a groupable field can take, in the field's order, with the empty key last where it applies. */
90
+ export function groupSpaceOf(def, options) {
91
+ if (def.type === "checkbox")
92
+ return [String(CHECKED), EMPTY_KEY];
93
+ const keys = options.map((o) => o.id);
94
+ // Total fields: every item has a status, a priority, a provenance and a workstream.
95
+ if (def.id === "status" || def.id === "priority" || def.id === "provenance" || def.id === "board")
96
+ return keys;
97
+ return [...keys, EMPTY_KEY];
98
+ }
99
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ export declare const itemKindSchema: z.ZodEnum<{
3
+ ticket: "ticket";
4
+ epic: "epic";
5
+ }>;
6
+ export declare const workTypeSchema: z.ZodEnum<{
7
+ coding: "coding";
8
+ general: "general";
9
+ }>;
10
+ export declare const itemStatusSchema: z.ZodEnum<{
11
+ backlog: "backlog";
12
+ todo: "todo";
13
+ doing: "doing";
14
+ verifying: "verifying";
15
+ done: "done";
16
+ cancelled: "cancelled";
17
+ }>;
18
+ export declare const prioritySchema: z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>]>;
19
+ export declare const listItemsFilterSchema: z.ZodObject<{
20
+ projectId: z.ZodOptional<z.ZodString>;
21
+ boardId: z.ZodOptional<z.ZodString>;
22
+ status: z.ZodOptional<z.ZodEnum<{
23
+ backlog: "backlog";
24
+ todo: "todo";
25
+ doing: "doing";
26
+ verifying: "verifying";
27
+ done: "done";
28
+ cancelled: "cancelled";
29
+ }>>;
30
+ kind: z.ZodOptional<z.ZodEnum<{
31
+ ticket: "ticket";
32
+ epic: "epic";
33
+ }>>;
34
+ workType: z.ZodOptional<z.ZodEnum<{
35
+ coding: "coding";
36
+ general: "general";
37
+ }>>;
38
+ priority: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
39
+ label: z.ZodOptional<z.ZodString>;
40
+ parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ search: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ export declare const fieldValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
44
+ /** A key set to null clears the value. */
45
+ export declare const fieldsPatchSchema: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
46
+ export declare const filterClauseSchema: z.ZodObject<{
47
+ field: z.ZodString;
48
+ op: z.ZodEnum<{
49
+ in: "in";
50
+ "not-in": "not-in";
51
+ contains: "contains";
52
+ gte: "gte";
53
+ lte: "lte";
54
+ }>;
55
+ values: z.ZodArray<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
56
+ }, z.core.$strip>;
57
+ export declare const sortKeySchema: z.ZodObject<{
58
+ field: z.ZodString;
59
+ dir: z.ZodEnum<{
60
+ asc: "asc";
61
+ desc: "desc";
62
+ }>;
63
+ }, z.core.$strip>;
64
+ export declare const createItemSchema: z.ZodObject<{
65
+ playbookId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
66
+ title: z.ZodString;
67
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
68
+ kind: z.ZodOptional<z.ZodEnum<{
69
+ ticket: "ticket";
70
+ epic: "epic";
71
+ }>>;
72
+ workType: z.ZodOptional<z.ZodEnum<{
73
+ coding: "coding";
74
+ general: "general";
75
+ }>>;
76
+ status: z.ZodOptional<z.ZodEnum<{
77
+ backlog: "backlog";
78
+ todo: "todo";
79
+ doing: "doing";
80
+ verifying: "verifying";
81
+ done: "done";
82
+ cancelled: "cancelled";
83
+ }>>;
84
+ priority: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
85
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
86
+ parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
+ projectId: z.ZodOptional<z.ZodString>;
88
+ boardId: z.ZodOptional<z.ZodString>;
89
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>>;
90
+ }, z.core.$strip>;
91
+ export declare const patchSchema: z.ZodObject<{
92
+ playbookId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
93
+ title: z.ZodOptional<z.ZodString>;
94
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
95
+ kind: z.ZodOptional<z.ZodEnum<{
96
+ ticket: "ticket";
97
+ epic: "epic";
98
+ }>>;
99
+ workType: z.ZodOptional<z.ZodEnum<{
100
+ coding: "coding";
101
+ general: "general";
102
+ }>>;
103
+ status: z.ZodOptional<z.ZodEnum<{
104
+ backlog: "backlog";
105
+ todo: "todo";
106
+ doing: "doing";
107
+ verifying: "verifying";
108
+ done: "done";
109
+ cancelled: "cancelled";
110
+ }>>;
111
+ priority: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>]>>;
112
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
113
+ blocks: z.ZodOptional<z.ZodArray<z.ZodString>>;
114
+ parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
115
+ rank: z.ZodOptional<z.ZodNumber>;
116
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>>;
117
+ }, z.core.$strip>;
118
+ //# sourceMappingURL=schema.d.ts.map
package/dist/schema.js ADDED
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ export const itemKindSchema = z.enum(["ticket", "epic"]);
3
+ export const workTypeSchema = z.enum(["coding", "general"]);
4
+ export const itemStatusSchema = z.enum(["backlog", "todo", "doing", "verifying", "done", "cancelled"]);
5
+ export const prioritySchema = z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3), z.literal(4)]);
6
+ export const listItemsFilterSchema = z.object({
7
+ projectId: z.string().optional(),
8
+ boardId: z.string().optional(),
9
+ status: itemStatusSchema.optional(),
10
+ kind: itemKindSchema.optional(),
11
+ workType: workTypeSchema.optional(),
12
+ priority: prioritySchema.optional(),
13
+ label: z.string().optional(),
14
+ parentId: z.string().nullable().optional(),
15
+ search: z.string().optional()
16
+ });
17
+ export const fieldValueSchema = z.union([z.string(), z.number()]);
18
+ /** A key set to null clears the value. */
19
+ export const fieldsPatchSchema = z.record(z.string(), fieldValueSchema.nullable());
20
+ export const filterClauseSchema = z.object({
21
+ field: z.string(),
22
+ op: z.enum(["in", "not-in", "contains", "gte", "lte"]),
23
+ values: z.array(fieldValueSchema.nullable())
24
+ });
25
+ export const sortKeySchema = z.object({ field: z.string(), dir: z.enum(["asc", "desc"]) });
26
+ export const createItemSchema = z.object({
27
+ playbookId: z.string().min(1).nullable().optional(),
28
+ title: z.string().min(1),
29
+ body: z.string().nullable().optional(),
30
+ kind: itemKindSchema.optional(),
31
+ workType: workTypeSchema.optional(),
32
+ status: itemStatusSchema.optional(),
33
+ priority: prioritySchema.optional(),
34
+ labels: z.array(z.string()).optional(),
35
+ parentId: z.string().nullable().optional(),
36
+ projectId: z.string().optional(),
37
+ boardId: z.string().optional(),
38
+ fields: fieldsPatchSchema.optional()
39
+ });
40
+ export const patchSchema = z.object({
41
+ playbookId: z.string().min(1).nullable().optional(),
42
+ title: z.string().optional(),
43
+ body: z.string().nullable().optional(),
44
+ kind: itemKindSchema.optional(),
45
+ workType: workTypeSchema.optional(),
46
+ status: itemStatusSchema.optional(),
47
+ priority: prioritySchema.optional(),
48
+ labels: z.array(z.string()).optional(),
49
+ blocks: z.array(z.string()).optional(),
50
+ parentId: z.string().nullable().optional(),
51
+ rank: z.number().optional(),
52
+ fields: fieldsPatchSchema.optional()
53
+ });
54
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,302 @@
1
+ import type { Layout, ViewQuery } from "./query.js";
2
+ import type { TicketEvidence } from "./delivery.js";
3
+ export type ItemKind = "ticket" | "epic";
4
+ export type WorkType = "coding" | "general";
5
+ export type ItemStatus = "backlog" | "todo" | "doing" | "verifying" | "done" | "cancelled";
6
+ export declare const ITEM_STATUSES: readonly ItemStatus[];
7
+ /** 0 None · 1 Low · 2 Medium (default) · 3 High · 4 Urgent. */
8
+ export type Priority = 0 | 1 | 2 | 3 | 4;
9
+ export declare const DEFAULT_PRIORITY: Priority;
10
+ /** Who wrote a change. Human edits are 'human'; every agent or plugin write is 'ai:<source>'. */
11
+ /** Who wrote: a person, a model, or a sync reading evidence in from outside (`sync:git` — a merge moved the ticket). */
12
+ export type Author = "human" | `ai:${string}` | `sync:${string}`;
13
+ /** Derived from updatedBy + humanLocked; never stored. */
14
+ export type Provenance = "human" | "ai" | "sync" | "locked";
15
+ /** A stored custom-field value. Dates are ISO `YYYY-MM-DD`; selects hold the option id. */
16
+ export type FieldValue = string | number;
17
+ export interface Item {
18
+ evidence: TicketEvidence;
19
+ id: string;
20
+ projectId: string;
21
+ /** The board that owns this issue. Dragging never changes it. */
22
+ boardId: string;
23
+ /** Per-project sequence behind the human key. Never reused. */
24
+ keyNum: number;
25
+ /** Display key, e.g. TOM-142. */
26
+ key: string;
27
+ title: string;
28
+ body: string | null;
29
+ kind: ItemKind;
30
+ workType: WorkType;
31
+ playbookId: string | null;
32
+ status: ItemStatus;
33
+ priority: Priority;
34
+ parentId: string | null;
35
+ rank: number;
36
+ labels: string[];
37
+ /** Ids of the items this one blocks. */
38
+ blocks: string[];
39
+ /** Ids of the items that block this one — the inverse of their `blocks`. */
40
+ blockedBy: string[];
41
+ /** Custom field values by field id. Built-in fields are the columns above. */
42
+ fields: Record<string, FieldValue>;
43
+ updatedAt: number;
44
+ updatedBy: Author;
45
+ version: number;
46
+ /** When set, AI writes may not touch title/body. */
47
+ humanLocked: boolean;
48
+ }
49
+ /** `fields` merges: a key set to null clears that value, absent keys are left alone. */
50
+ export type Patch = Partial<Pick<Item, "title" | "body" | "kind" | "workType" | "playbookId" | "status" | "priority" | "parentId" | "rank" | "boardId" | "labels" | "blocks" | "humanLocked">> & {
51
+ fields?: Record<string, FieldValue | null>;
52
+ };
53
+ export type OplogEntity = "item" | "board" | "view";
54
+ /**
55
+ * 'delete' is reversible — the row carries the snapshot undo restores from.
56
+ * 'purge' is a human-only permanent destroy: the entity's own history goes with
57
+ * it and the tombstone carries no snapshot, so undo walks straight past it.
58
+ * 'rank' is one item's manual position in one view — logged so it travels and
59
+ * can be undone, kept apart so history reads as decisions, not drags.
60
+ * 'merge' is a note a sync left behind: the same field changed on two machines,
61
+ * `before` holds the value that lost and `after` the one that stands. Undo
62
+ * steps over it; taking the loser back is an ordinary edit.
63
+ */
64
+ export type OplogOp = "create" | "update" | "delete" | "purge" | "rank" | "merge";
65
+ export interface OplogEntry {
66
+ seq: number;
67
+ entity: OplogEntity;
68
+ entityId: string;
69
+ op: OplogOp;
70
+ baseVersion: number | null;
71
+ before: unknown | null;
72
+ after: unknown | null;
73
+ by: Author;
74
+ at: number;
75
+ /** The agent run this write belongs to; null for a write made outside one. */
76
+ runId: string | null;
77
+ /** The machine that made the write; `device:deviceSeq` is the op's identity across machines. */
78
+ device: string;
79
+ /** The op's position in its own machine's sequence; null only for rows written before devices existed. */
80
+ deviceSeq: number | null;
81
+ /** The fields the op changed (the whole entity for a create); what the folder's log carries. */
82
+ patch: Record<string, unknown> | null;
83
+ /** The extension that made the write; null for the app itself. Local to this machine. */
84
+ via: string | null;
85
+ }
86
+ export interface UndoResult {
87
+ entry: OplogEntry;
88
+ /** One line describing what was reverted, for the toast. */
89
+ summary: string;
90
+ }
91
+ /** Result of reverting one agent run. `kept` names writes left in place because someone edited the entity afterwards. */
92
+ export interface UndoRunResult {
93
+ reverted: number;
94
+ kept: {
95
+ entity: OplogEntity;
96
+ entityId: string;
97
+ label: string;
98
+ reason: string;
99
+ }[];
100
+ }
101
+ /**
102
+ * One record of the project's history as the timeline shows it: a backlog
103
+ * write, a file operation, or anything a plugin logs through its own undo
104
+ * provider. `ref` (`provider:id`) names it to `oplog.revert` and `oplog.rollback`.
105
+ */
106
+ export interface UndoRecord {
107
+ ref: string;
108
+ provider: string;
109
+ id: string;
110
+ /** What kind of thing it touched: `item`, `board`, `view`, `file`, or a plugin's own. */
111
+ entity: string;
112
+ entityId: string;
113
+ /** What happened to it: an oplog op, or a file op (`write`, `mkdir`, `move`, `copy`, `remove`, `import`). */
114
+ op: string;
115
+ /** The entity's name for a sentence: an issue key, a view name, a file path. */
116
+ label: string;
117
+ /** A second name when the op has one — where a file moved or was copied to. */
118
+ target: string | null;
119
+ by: Author;
120
+ at: number;
121
+ runId: string | null;
122
+ /** The machine that made it; a record from another machine is history, never undoable here. */
123
+ device: string;
124
+ /** False for purges, merge notes and other machines' writes. */
125
+ undoable: boolean;
126
+ /** Set while the record sits in the redo stack; a new write anywhere clears that stack. */
127
+ undoneAt: number | null;
128
+ }
129
+ /** One step the timeline took: the record it acted on and a line for the toast. */
130
+ export interface UndoStep {
131
+ record: UndoRecord;
132
+ summary: string;
133
+ }
134
+ /** Moving the timeline's cursor to a record: the steps taken, and why it stopped short when it did. */
135
+ export interface RollbackResult {
136
+ steps: UndoStep[];
137
+ stopped: string | null;
138
+ }
139
+ export interface CreateItemInput {
140
+ projectId?: string;
141
+ boardId: string;
142
+ title: string;
143
+ body?: string | null;
144
+ kind?: ItemKind;
145
+ workType?: WorkType;
146
+ playbookId?: string | null;
147
+ status?: ItemStatus;
148
+ priority?: Priority;
149
+ parentId?: string | null;
150
+ rank?: number;
151
+ labels?: string[];
152
+ fields?: Record<string, FieldValue | null>;
153
+ by: Author;
154
+ }
155
+ export interface ListItemsFilter {
156
+ projectId?: string;
157
+ boardId?: string;
158
+ status?: ItemStatus;
159
+ kind?: ItemKind;
160
+ workType?: WorkType;
161
+ priority?: Priority;
162
+ parentId?: string | null;
163
+ label?: string;
164
+ /** Words that must all appear in title or body, as substrings in any script; matches come most relevant first. */
165
+ search?: string;
166
+ /** At most this many rows, from the front of the order: the most relevant when searching. */
167
+ limit?: number;
168
+ /** When no item has every search term, fall back to items with some of them, best matches first. */
169
+ widen?: boolean;
170
+ }
171
+ export interface Project {
172
+ id: string;
173
+ name: string;
174
+ description: string | null;
175
+ /** Prefix of the human issue key, e.g. 'TOM' in TOM-142. */
176
+ keyPrefix: string;
177
+ archived: boolean;
178
+ createdAt: number;
179
+ updatedAt: number;
180
+ }
181
+ export interface CreateProjectInput {
182
+ name: string;
183
+ description?: string | null;
184
+ keyPrefix?: string;
185
+ }
186
+ export type ProjectPatch = Partial<Pick<Project, "name" | "description" | "archived">>;
187
+ /**
188
+ * A board is a workstream — an option of the built-in Workstream field. Every
189
+ * issue belongs to exactly one; a drag on a Workstream column writes it like
190
+ * any other field.
191
+ */
192
+ export interface Board {
193
+ id: string;
194
+ projectId: string;
195
+ name: string;
196
+ icon: string;
197
+ note: string | null;
198
+ rank: number;
199
+ /** The code repository this workstream stands for, if any; several workstreams may share one. */
200
+ repoId: string | null;
201
+ /** Where in that repository the workstream's code lives; '' for the root. */
202
+ repoPath: string;
203
+ createdAt: number;
204
+ updatedAt: number;
205
+ }
206
+ export interface CreateBoardInput {
207
+ projectId: string;
208
+ name: string;
209
+ icon?: string;
210
+ note?: string | null;
211
+ rank?: number;
212
+ repoId?: string | null;
213
+ repoPath?: string;
214
+ }
215
+ export type BoardPatch = Partial<Pick<Board, "name" | "icon" | "note" | "rank" | "repoId" | "repoPath">>;
216
+ /**
217
+ * A code repository a project's workstreams stand for. Shared: the remote URL
218
+ * is its identity everywhere. Where it is checked out on this machine is the
219
+ * catalog's business, never a row here.
220
+ */
221
+ export interface Repo {
222
+ id: string;
223
+ projectId: string;
224
+ remote: string;
225
+ name: string;
226
+ defaultBranch: string;
227
+ createdAt: number;
228
+ updatedAt: number;
229
+ }
230
+ export interface CreateRepoInput {
231
+ projectId: string;
232
+ remote: string;
233
+ name?: string;
234
+ defaultBranch?: string;
235
+ }
236
+ export type RepoPatch = Partial<Pick<Repo, "name" | "defaultBranch" | "remote">>;
237
+ /** A saved, named way of looking at the project's backlog: one layout and one query. */
238
+ export interface View {
239
+ id: string;
240
+ projectId: string;
241
+ name: string;
242
+ icon: string;
243
+ layout: Layout;
244
+ query: ViewQuery;
245
+ rank: number;
246
+ createdAt: number;
247
+ updatedAt: number;
248
+ }
249
+ export interface CreateViewInput {
250
+ projectId: string;
251
+ name: string;
252
+ icon?: string;
253
+ layout?: Layout;
254
+ query?: Partial<ViewQuery>;
255
+ rank?: number;
256
+ }
257
+ export type ViewPatch = Partial<Pick<View, "name" | "icon" | "layout" | "query" | "rank">>;
258
+ export type BuiltInTheme = "light" | "dark" | "matcha" | "dawn";
259
+ export type ThemePreference = "system" | BuiltInTheme | `custom:${string}`;
260
+ export declare const THEME_TOKEN_NAMES: readonly ["bg", "surface", "surface-warm", "fg", "fg-2", "muted", "meta", "border", "border-soft", "accent", "accent-on", "accent-light", "accent-active", "success", "warn", "danger", "tag-bg-faint", "tag-bg-soft", "tag-bg-base", "tag-bg-strong", "tag-fg"];
261
+ export type ThemeTokenName = (typeof THEME_TOKEN_NAMES)[number];
262
+ export declare const THEME_FONT_NAMES: readonly ["font-display", "font-body", "font-mono"];
263
+ export type ThemeFontName = (typeof THEME_FONT_NAMES)[number];
264
+ export declare const THEME_FONT_SIZE_NAMES: readonly ["text-xs", "text-sm", "text-base", "text-md", "text-lg", "text-xl", "text-2xl"];
265
+ export declare const THEME_FONT_WEIGHT_NAMES: readonly ["font-weight-regular", "font-weight-medium"];
266
+ export declare const THEME_TYPOGRAPHY_NAMES: readonly ["font-display", "font-body", "font-mono", "text-xs", "text-sm", "text-base", "text-md", "text-lg", "text-xl", "text-2xl", "font-weight-regular", "font-weight-medium"];
267
+ export type ThemeTypographyName = (typeof THEME_TYPOGRAPHY_NAMES)[number];
268
+ export interface CustomTheme {
269
+ id: `custom:${string}`;
270
+ name: string;
271
+ base: BuiltInTheme;
272
+ icon: "i-sun" | "i-moon" | "i-leaf" | "i-tag" | "i-bug" | "i-board" | "i-stack" | "i-sliders";
273
+ tokens: Record<ThemeTokenName, string>;
274
+ fonts: Record<ThemeTypographyName, string>;
275
+ }
276
+ export type ThemeAutomationMode = "none" | "system" | "schedule" | "shuffle";
277
+ export interface ThemeScheduleRule {
278
+ start: string;
279
+ end: string;
280
+ theme: Exclude<ThemePreference, "system">;
281
+ }
282
+ export interface ThemeAutomation {
283
+ mode: ThemeAutomationMode;
284
+ defaultTheme: Exclude<ThemePreference, "system">;
285
+ schedule: ThemeScheduleRule[];
286
+ shuffleMinutes: number;
287
+ }
288
+ /** 'system' follows the OS locale, falling back to English when that locale isn't supported. */
289
+ export type LanguagePreference = "system" | "en" | "zh";
290
+ export interface LlmSettings {
291
+ apiKey: string;
292
+ baseURL: string;
293
+ model: string;
294
+ }
295
+ /** Settings are sections keyed by id; plugins register their own. */
296
+ export interface AppSettings {
297
+ theme: ThemePreference;
298
+ language: LanguagePreference;
299
+ llm?: LlmSettings;
300
+ [section: string]: unknown;
301
+ }
302
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ export const ITEM_STATUSES = ["backlog", "todo", "doing", "verifying", "done", "cancelled"];
2
+ export const DEFAULT_PRIORITY = 2;
3
+ export const THEME_TOKEN_NAMES = ["bg", "surface", "surface-warm", "fg", "fg-2", "muted", "meta", "border", "border-soft", "accent", "accent-on", "accent-light", "accent-active", "success", "warn", "danger", "tag-bg-faint", "tag-bg-soft", "tag-bg-base", "tag-bg-strong", "tag-fg"];
4
+ export const THEME_FONT_NAMES = ["font-display", "font-body", "font-mono"];
5
+ export const THEME_FONT_SIZE_NAMES = ["text-xs", "text-sm", "text-base", "text-md", "text-lg", "text-xl", "text-2xl"];
6
+ export const THEME_FONT_WEIGHT_NAMES = ["font-weight-regular", "font-weight-medium"];
7
+ export const THEME_TYPOGRAPHY_NAMES = [...THEME_FONT_NAMES, ...THEME_FONT_SIZE_NAMES, ...THEME_FONT_WEIGHT_NAMES];
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,13 @@
1
+ import type { GroupBy, ViewKind } from "./types.js";
2
+ /**
3
+ * The `views` table still carries legacy `name`/`icon`/`group_by` columns
4
+ * (kept for schema stability — see packages/db). These are the values written
5
+ * into them for each built-in kind; the app itself never reads them back.
6
+ */
7
+ export declare const VIEW_KIND_META: Record<ViewKind, {
8
+ name: string;
9
+ icon: string;
10
+ groupBy: GroupBy;
11
+ rank: number;
12
+ }>;
13
+ //# sourceMappingURL=view-kinds.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The `views` table still carries legacy `name`/`icon`/`group_by` columns
3
+ * (kept for schema stability — see packages/db). These are the values written
4
+ * into them for each built-in kind; the app itself never reads them back.
5
+ */
6
+ export const VIEW_KIND_META = {
7
+ status: { name: "Status", icon: "s-doing", groupBy: "status", rank: 0 },
8
+ priority: { name: "Priority", icon: "i-bars", groupBy: "priority", rank: 1 },
9
+ epic: { name: "Epic", icon: "i-stack", groupBy: "epic", rank: 2 },
10
+ dependencies: { name: "Dependencies", icon: "i-graph", groupBy: "status", rank: 3 }
11
+ };
12
+ //# sourceMappingURL=view-kinds.js.map