@crossworks/client-types 0.232.245 → 0.232.246

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossworks/client-types",
3
- "version": "0.232.245",
3
+ "version": "0.232.246",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/app-nav.ts ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * App navigation: how the owner organises their mini apps in the sidebar.
3
+ *
4
+ * Two halves with different owners:
5
+ *
6
+ * - The LAYOUT (`AppNav`: folders, nesting, order) is BRAIN-level. It is one
7
+ * shared record on the anchor row (see BRAIN_PREFERENCE_KEYS), so every admin
8
+ * and every client (web, desktop, phone) renders the same tree.
9
+ * - PINS and OPEN COUNTS are per-login: what one person keeps at hand and what
10
+ * they actually use.
11
+ *
12
+ * App icon and colour live on the app itself (`AppRow.icon` / `AppRow.color`),
13
+ * not here, so every surface that lists an app shows the same face.
14
+ *
15
+ * Pure (no React, no runtime deps) so the server, the web client and any other
16
+ * client can share the types and the limits.
17
+ */
18
+
19
+ /**
20
+ * Named tints for app and folder tiles. We store the KEY, never a colour: each
21
+ * theme defines its own `--app-tint-<key>` / `--app-tint-<key>-ink` pair, so a
22
+ * tile stays legible on every generated theme and in both modes.
23
+ */
24
+ export const APP_TINTS = [
25
+ 'slate',
26
+ 'red',
27
+ 'orange',
28
+ 'amber',
29
+ 'lime',
30
+ 'green',
31
+ 'teal',
32
+ 'sky',
33
+ 'blue',
34
+ 'indigo',
35
+ 'violet',
36
+ 'pink',
37
+ ] as const;
38
+ export type AppTint = (typeof APP_TINTS)[number];
39
+
40
+ export function isAppTint(v: unknown): v is AppTint {
41
+ return typeof v === 'string' && (APP_TINTS as readonly string[]).includes(v);
42
+ }
43
+
44
+ /**
45
+ * An app or folder icon is either an emoji (the historical form, up to 16
46
+ * UTF-16 units so flags and ZWJ sequences fit) or `lucide:<name>` naming an
47
+ * icon from the client's curated set. The server checks the SHAPE only; a
48
+ * client that doesn't know a lucide name falls back to its default tile.
49
+ */
50
+ export const LUCIDE_ICON_PREFIX = 'lucide:';
51
+ export const APP_ICON_EMOJI_MAX = 16;
52
+ export const APP_ICON_MAX = 48;
53
+
54
+ /** Folders nest at most this deep: folder › subfolder › sub-subfolder. */
55
+ export const APP_NAV_MAX_DEPTH = 3;
56
+ /** Across the whole tree. Generous; it bounds the stored jsonb, not taste. */
57
+ export const APP_NAV_MAX_FOLDERS = 200;
58
+ export const APP_NAV_FOLDER_NAME_MAX = 60;
59
+ /** Personal pins shown above the tree. */
60
+ export const APP_PINS_MAX = 12;
61
+ /** Personal sidebar favourites (nav hrefs). */
62
+ export const NAV_FAVORITES_MAX = 40;
63
+ export const NAV_FAVORITE_HREF_MAX = 200;
64
+ /** Per-login open counters kept; the least recently opened fall off. */
65
+ export const APP_OPENS_MAX = 300;
66
+
67
+ export type AppNavApp = { kind: 'app'; id: string };
68
+
69
+ export type AppNavFolder = {
70
+ kind: 'folder';
71
+ /** Client-generated UUID, stable across renames and moves. */
72
+ id: string;
73
+ name: string;
74
+ icon?: string;
75
+ color?: AppTint;
76
+ children: AppNavEntry[];
77
+ };
78
+
79
+ export type AppNavEntry = AppNavApp | AppNavFolder;
80
+
81
+ /**
82
+ * The shared layout. `entries` is the root level in display order; each folder
83
+ * carries its own ordered children, so order and nesting are one structure and
84
+ * a dangling parent or a cycle can't be expressed.
85
+ *
86
+ * An app that appears nowhere in the tree is UNSORTED: clients list those after
87
+ * the tree (newest first), which is where a freshly created app lands.
88
+ *
89
+ * `rev` increments on every save. A save names the rev it was based on and is
90
+ * refused (409) when another client saved first, so two devices can't silently
91
+ * overwrite each other's reorganisation.
92
+ */
93
+ export type AppNav = { rev: number; entries: AppNavEntry[] };
94
+
95
+ export const EMPTY_APP_NAV: AppNav = { rev: 0, entries: [] };
96
+
97
+ /** One login's use of one app: open count and last-opened ISO instant. */
98
+ export type AppOpenStat = { n: number; at: string };
99
+
100
+ /** Slim app row for navigation surfaces: everything the tree needs to render
101
+ * and search, nothing it doesn't. */
102
+ export type AppNavItem = {
103
+ id: string;
104
+ title: string;
105
+ icon: string | null;
106
+ color: AppTint | null;
107
+ tags: string[];
108
+ description: string | null;
109
+ /** Previewable: a green published or draft build (the frame-ticket test).
110
+ * Differs from AppRow.hasBuild, which is published-only. */
111
+ hasBuild: boolean;
112
+ updatedAt: string;
113
+ };
114
+
115
+ /** GET /api/app-nav — the whole sidebar in one round-trip. `nav` is already
116
+ * pruned to apps that still exist, and `pins` likewise. */
117
+ export type AppNavResponse = {
118
+ nav: AppNav;
119
+ pins: string[];
120
+ opens: Record<string, AppOpenStat>;
121
+ apps: AppNavItem[];
122
+ };
package/src/dto/rows.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  * public surface is byte-identical — only the file a symbol lives in moved.
11
11
  */
12
12
 
13
+ import type { AppNav, AppOpenStat, AppTint } from '../app-nav';
14
+
13
15
  // ── Row/DTO shapes moved from the server packages (jackdaw split P0) ─────────
14
16
  // Sources: @mantle/content, @mantle/email, @mantle/microsoft, @mantle/runtime/agent
15
17
  // re-export these names, so server code keeps its original import paths.
@@ -148,7 +150,10 @@ export type PageListRow = PageRow & {
148
150
  export type AppRow = {
149
151
  id: string;
150
152
  title: string;
153
+ /** Emoji, or `lucide:<name>` from the client's curated set (app-nav.ts). */
151
154
  icon: string | null;
155
+ /** Tile tint key (APP_TINTS); null = the client's neutral default. */
156
+ color: AppTint | null;
152
157
  tags: string[];
153
158
  summary: string | null;
154
159
  description: string | null;
@@ -450,6 +455,18 @@ export type ProfilePreferences = {
450
455
  * this pref only chooses which tag groupings get pinned. Unset/empty ⇒ no
451
456
  * curated sections. Read via projectTeamHubTags, never raw. */
452
457
  teamHubTags?: string[];
458
+ /** BRAIN-level sidebar layout for mini apps: folders (nested up to
459
+ * APP_NAV_MAX_DEPTH), their order, and where each app sits. Written ONLY
460
+ * through saveAppNav (rev-checked), read via projectAppNav, never raw. */
461
+ appNav?: AppNav;
462
+ /** Per-login: app ids pinned above the tree, in order. */
463
+ appPins?: string[];
464
+ /** Per-login: sidebar favourite hrefs, in order. Moved server-side from the
465
+ * web client's localStorage so they follow the person across devices. */
466
+ navFavorites?: string[];
467
+ /** Per-login: open count + last-opened instant per app id, feeding the
468
+ * "Most used" and "Recent" filters. Written by recordAppOpen. */
469
+ appOpens?: Record<string, AppOpenStat>;
453
470
  };
454
471
 
455
472
  export type BackupConfig = {
package/src/index.ts CHANGED
@@ -32,3 +32,16 @@ export * from './dto/turns';
32
32
  export * from './dto/rows';
33
33
  export * from './dto/views';
34
34
  export * from './dto/recall';
35
+
36
+ // Types only at the root; the runtime constants (APP_TINTS, limits) are the
37
+ // `@mantle/client-types/app-nav` subpath.
38
+ export type {
39
+ AppNav,
40
+ AppNavApp,
41
+ AppNavEntry,
42
+ AppNavFolder,
43
+ AppNavItem,
44
+ AppNavResponse,
45
+ AppOpenStat,
46
+ AppTint,
47
+ } from './app-nav';