@treeseed/core 0.8.19 → 0.9.4

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.
@@ -9,16 +9,53 @@ type LocalContributor = {
9
9
  };
10
10
  };
11
11
 
12
+ type MetadataItem = {
13
+ label: string;
14
+ value?: unknown;
15
+ href?: string;
16
+ };
17
+
12
18
  function entryTitle(entry: RuntimeReferenceEntry) {
13
19
  return entry.data.title ?? entry.data.name ?? entry.id;
14
20
  }
15
21
 
22
+ function displayValue(value: unknown) {
23
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
24
+ if (Array.isArray(value)) return value.map((entry) => displayValue(entry)).filter(Boolean).join(', ');
25
+ if (typeof value === 'string') return value.trim();
26
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
27
+ return '';
28
+ }
29
+
30
+ function metadataItem(label: string, value: unknown, href?: string): MetadataItem | null {
31
+ const display = displayValue(value);
32
+ return display ? { label, value: display, href } : null;
33
+ }
34
+
35
+ function metadataKey(item: MetadataItem) {
36
+ return `${item.label.toLowerCase()}::${displayValue(item.value).toLowerCase()}::${item.href ?? ''}`;
37
+ }
38
+
39
+ function uniqueMetadata(items: MetadataItem[]) {
40
+ const seen = new Set<string>();
41
+ return items.filter((item) => {
42
+ const key = metadataKey(item);
43
+ if (seen.has(key)) return false;
44
+ seen.add(key);
45
+ return true;
46
+ });
47
+ }
48
+
16
49
  const {
17
50
  entry,
18
51
  currentPath,
19
52
  contributor,
53
+ contentTypeLabel = 'Content',
54
+ contentId,
55
+ collectionLabel,
20
56
  metaLabel,
21
57
  metaValue,
58
+ metadataItems = [],
22
59
  relatedQuestions = [],
23
60
  relatedObjectives = [],
24
61
  relatedNotes = [],
@@ -35,11 +72,17 @@ const {
35
72
  date: Date;
36
73
  motivation?: string;
37
74
  tags: string[];
75
+ canonicalRoute?: string;
76
+ author?: string;
38
77
  };
39
78
  currentPath: string;
40
79
  contributor: LocalContributor | RuntimeReferenceEntry | undefined | null;
80
+ contentTypeLabel?: string;
81
+ contentId?: string;
82
+ collectionLabel?: string;
41
83
  metaLabel?: string;
42
84
  metaValue?: string;
85
+ metadataItems?: MetadataItem[];
43
86
  relatedQuestions?: RuntimeReferenceEntry[];
44
87
  relatedObjectives?: RuntimeReferenceEntry[];
45
88
  relatedNotes?: RuntimeReferenceEntry[];
@@ -48,88 +91,105 @@ const {
48
91
  relatedBooks?: RuntimeReferenceEntry[];
49
92
  introText?: string;
50
93
  };
94
+
95
+ const contributorName = contributor?.data.name ?? entry.author ?? '';
96
+ const normalizedTypeLabel = contentTypeLabel.trim() || 'Content';
97
+ const contentKind = displayValue(metaValue);
98
+ const recordMetadata = uniqueMetadata([
99
+ metadataItem('ID', contentId),
100
+ metadataItem('Collection', collectionLabel ?? (currentPath.replaceAll('/', '') || undefined)),
101
+ metadataItem(metaLabel ?? '', metaValue),
102
+ metadataItem('Route', entry.canonicalRoute, entry.canonicalRoute),
103
+ ...metadataItems.map((item) => metadataItem(item.label, item.value, item.href)).filter(Boolean),
104
+ ].filter(Boolean) as MetadataItem[]);
105
+
106
+ const relationSections = [
107
+ { label: 'Questions', hrefBase: '/questions', items: relatedQuestions },
108
+ { label: 'Objectives', hrefBase: '/objectives', items: relatedObjectives },
109
+ { label: 'Notes', hrefBase: '/notes', items: relatedNotes },
110
+ { label: 'Proposals', hrefBase: '/proposals', items: relatedProposals },
111
+ { label: 'Decisions', hrefBase: '/decisions', items: relatedDecisions },
112
+ { label: 'Books', hrefBase: '/books', items: relatedBooks },
113
+ ].filter((section) => section.items.length > 0);
114
+
115
+ const relationCount = relationSections.reduce((total, section) => total + section.items.length, 0);
116
+ const plainIntroText = displayValue(introText ?? entry.motivation);
51
117
  ---
52
118
 
53
119
  <MainLayout title={entry.title} description={entry.description} currentPath={currentPath}>
54
- <article class="max-w-4xl space-y-8">
55
- <div class="space-y-4 border-b border-[color:var(--ts-color-border)] pb-8">
56
- <div class="flex flex-wrap items-center gap-3">
57
- <StatusBadge status={entry.status} />
58
- <p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">{entry.date.toISOString().slice(0, 10)}</p>
59
- {contributor && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{contributor.data.name}</p>}
60
- {metaLabel && metaValue && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{metaLabel}: {metaValue}</p>}
120
+ <article class="max-w-6xl space-y-8">
121
+ <header class="grid gap-6 border-b border-[color:var(--ts-color-border)] pb-7 lg:grid-cols-[minmax(0,1fr)_18rem]">
122
+ <div class="space-y-4">
123
+ <div class="flex flex-wrap items-center gap-2.5">
124
+ <p class="rounded-full border border-[color:var(--ts-color-border)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-info-text)]">{normalizedTypeLabel}</p>
125
+ {contentKind && <p class="rounded-full bg-[color:var(--ts-color-surface-muted)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-text-subtle)]">{contentKind}</p>}
126
+ <StatusBadge status={entry.status} />
127
+ <p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">{entry.date.toISOString().slice(0, 10)}</p>
128
+ {contributorName && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{contributorName}</p>}
129
+ </div>
130
+ <h1 class="max-w-4xl font-serif text-4xl font-bold tracking-normal text-[color:var(--ts-color-text)] md:text-5xl">{entry.title}</h1>
131
+ <p class="max-w-3xl text-lg leading-8 text-[color:var(--ts-color-text-muted)] md:text-xl md:leading-9">{entry.summary}</p>
132
+ {plainIntroText && (
133
+ <div class="max-w-3xl border-l-2 border-[color:var(--ts-color-border-strong)] pl-4">
134
+ <p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Why it matters</p>
135
+ <p class="mt-1 text-sm leading-7 text-[color:var(--ts-color-text-muted)]">{plainIntroText}</p>
136
+ </div>
137
+ )}
138
+ {entry.tags.length > 0 && (
139
+ <ul class="flex flex-wrap gap-2" aria-label="Tags">
140
+ {entry.tags.map((tag) => (
141
+ <li class="rounded-full bg-[color:var(--ts-color-surface-muted)] px-3 py-1 text-xs font-semibold uppercase text-[color:var(--ts-color-accent-strong)]">{tag}</li>
142
+ ))}
143
+ </ul>
144
+ )}
61
145
  </div>
62
- <h1 class="max-w-4xl font-serif text-5xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-6xl">{entry.title}</h1>
63
- <p class="max-w-3xl text-xl leading-10 text-[color:var(--ts-color-text-muted)]">{entry.summary}</p>
64
- {(introText ?? entry.motivation) && <p class="max-w-3xl text-base leading-8 text-[color:var(--ts-color-text-muted)]">{introText ?? entry.motivation}</p>}
65
- {entry.tags.length > 0 && (
66
- <p class="text-sm uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{entry.tags.join(' / ')}</p>
67
- )}
68
- </div>
69
- <div class="prose-karyon">
146
+ <aside class="self-start border-l border-[color:var(--ts-color-border)] pl-5">
147
+ <div class="flex items-center justify-between gap-3 border-b border-[color:var(--ts-color-border)] pb-3">
148
+ <div>
149
+ <p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Record</p>
150
+ <p class="mt-1 text-sm text-[color:var(--ts-color-text-muted)]">{relationCount} linked record{relationCount === 1 ? '' : 's'}</p>
151
+ </div>
152
+ </div>
153
+ <dl class="mt-2 text-sm">
154
+ {recordMetadata.map((item) => (
155
+ <div class="grid grid-cols-[5.75rem_minmax(0,1fr)] gap-3 py-2.5">
156
+ <dt class="text-[color:var(--ts-color-text-subtle)]">{item.label}</dt>
157
+ <dd class="min-w-0 break-words font-medium text-[color:var(--ts-color-text)]">
158
+ {item.href ? <a class="underline decoration-[color:var(--ts-color-border-strong)] underline-offset-4 hover:text-[color:var(--ts-color-accent-strong)]" href={item.href}>{displayValue(item.value)}</a> : displayValue(item.value)}
159
+ </dd>
160
+ </div>
161
+ ))}
162
+ </dl>
163
+ </aside>
164
+ </header>
165
+ <div class="prose-karyon max-w-3xl">
70
166
  <slot />
71
167
  </div>
72
- <div class="grid gap-6 border-t border-[color:var(--ts-color-border)] pt-8 md:grid-cols-3">
73
- {relatedQuestions.length > 0 && (
74
- <div>
75
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related questions</p>
76
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
77
- {relatedQuestions.map((question) => (
78
- <li><a href={`/questions/${question.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(question)}</a></li>
79
- ))}
80
- </ul>
81
- </div>
82
- )}
83
- {relatedObjectives.length > 0 && (
84
- <div>
85
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related objectives</p>
86
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
87
- {relatedObjectives.map((objective) => (
88
- <li><a href={`/objectives/${objective.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(objective)}</a></li>
89
- ))}
90
- </ul>
168
+ {relationSections.length > 0 && (
169
+ <section class="border-t border-[color:var(--ts-color-border)] pt-7">
170
+ <div class="mb-4 flex flex-wrap items-end justify-between gap-3">
171
+ <div>
172
+ <p class="text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Connections</p>
91
173
  </div>
92
- )}
93
- {relatedNotes.length > 0 && (
94
- <div>
95
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related notes</p>
96
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
97
- {relatedNotes.map((note) => (
98
- <li><a href={`/notes/${note.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(note)}</a></li>
99
- ))}
100
- </ul>
101
- </div>
102
- )}
103
- {relatedProposals.length > 0 && (
104
- <div>
105
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related proposals</p>
106
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
107
- {relatedProposals.map((proposal) => (
108
- <li><a href={`/proposals/${proposal.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(proposal)}</a></li>
109
- ))}
110
- </ul>
111
- </div>
112
- )}
113
- {relatedDecisions.length > 0 && (
114
- <div>
115
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related decisions</p>
116
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
117
- {relatedDecisions.map((decision) => (
118
- <li><a href={`/decisions/${decision.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(decision)}</a></li>
119
- ))}
120
- </ul>
121
- </div>
122
- )}
123
- {relatedBooks.length > 0 && (
124
- <div>
125
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">Related books</p>
126
- <ul class="mt-3 space-y-2 text-[color:var(--ts-color-text-muted)]">
127
- {relatedBooks.map((book) => (
128
- <li><a href={`/books/${book.id}/`} class="hover:text-[color:var(--ts-color-text)]">{entryTitle(book)}</a></li>
129
- ))}
130
- </ul>
131
- </div>
132
- )}
133
- </div>
174
+ </div>
175
+ <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
176
+ {relationSections.map((section) => (
177
+ <div class="border-l border-[color:var(--ts-color-border)] pl-4">
178
+ <p class="text-xs font-semibold uppercase tracking-[0.12em] text-[color:var(--ts-color-text-subtle)]">{section.label}</p>
179
+ <ul class="mt-2 space-y-2">
180
+ {section.items.map((item) => (
181
+ <li>
182
+ <a href={`${section.hrefBase}/${item.id}/`} class="block text-[color:var(--ts-color-text)] hover:text-[color:var(--ts-color-accent-strong)]">
183
+ <span class="block text-sm font-semibold">{entryTitle(item)}</span>
184
+ <span class="block text-xs text-[color:var(--ts-color-text-subtle)]">{item.id}</span>
185
+ </a>
186
+ </li>
187
+ ))}
188
+ </ul>
189
+ </div>
190
+ ))}
191
+ </div>
192
+ </section>
193
+ )}
134
194
  </article>
135
195
  </MainLayout>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import '../styles/global.css';
3
3
  import PublicShell from '../components/ui/shell/PublicShell.astro';
4
+ import ShellIconLink from '../components/ui/shell/ShellIconLink.astro';
4
5
  import { SITE_NAV_GROUPS } from '../utils/routes';
5
6
  import { SITE } from '../utils/seo';
6
7
  import { SITE_THEME_CSS } from '../utils/site-config';
@@ -36,6 +37,7 @@ const navItems = SITE_NAV_GROUPS.flatMap((group) => group.items);
36
37
  {SITE_THEME_CSS && <style is:global>{SITE_THEME_CSS}</style>}
37
38
  </Fragment>
38
39
  <Fragment slot="actions">
40
+ <ShellIconLink href="/app/" label="Manager" icon="manager" />
39
41
  <a
40
42
  href={SITE.githubRepository}
41
43
  target="_blank"
@@ -57,9 +57,17 @@ const supersededDecisions = publishedRuntime
57
57
  entry={decision.data}
58
58
  currentPath="/decisions/"
59
59
  contributor={contributor}
60
+ contentTypeLabel="Decision"
61
+ contentId={decision.id}
62
+ collectionLabel="decisions"
60
63
  metaLabel="Decision type"
61
64
  metaValue={String(decision.data.decisionType ?? '')}
62
- introText={`${decision.data.rationale ?? ''} Authority: ${decision.data.authority ?? ''}.`}
65
+ introText={decision.data.rationale}
66
+ metadataItems={[
67
+ { label: 'Authority', value: decision.data.authority },
68
+ { label: 'Route', value: decision.data.canonicalRoute, href: decision.data.canonicalRoute },
69
+ { label: 'Implements', value: decision.data.implements },
70
+ ]}
63
71
  relatedObjectives={relatedObjectives}
64
72
  relatedQuestions={relatedQuestions}
65
73
  relatedNotes={relatedNotes}
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  import { getCollection, render } from 'astro:content';
3
- import NoteLayout from '../../layouts/NoteLayout.astro';
3
+ import AuthoredEntryLayout from '../../layouts/AuthoredEntryLayout.astro';
4
4
  import PublishedContentBody from '../../components/site/PublishedContentBody.astro';
5
5
  import RouteNotFound from '../../components/site/RouteNotFound.astro';
6
- import { isPublishedRuntimeContentMode, loadPublishedEntry } from '../../utils/site-content-runtime';
6
+ import { isPublishedRuntimeContentMode, loadPublishedEntry, metadataFromPublishedContent } from '../../utils/site-content-runtime';
7
7
 
8
8
  export const prerender = false;
9
9
 
@@ -13,6 +13,7 @@ const notes = publishedRuntime ? [] : await getCollection('notes', ({ data }) =>
13
13
  const localNote = publishedRuntime ? null : notes.find((candidate) => candidate.id === slug) ?? null;
14
14
  const publishedNote = publishedRuntime ? await loadPublishedEntry(Astro.locals, 'notes', slug) : null;
15
15
  const note = publishedRuntime ? publishedNote?.entry ?? null : localNote;
16
+ const metadata = publishedRuntime ? metadataFromPublishedContent(publishedNote?.content) : null;
16
17
  if (!note) {
17
18
  Astro.response.status = 404;
18
19
  }
@@ -24,8 +25,20 @@ const Content = rendered?.Content ?? null;
24
25
  !note || (!Content && !publishedNote?.html) ? (
25
26
  <RouteNotFound title="Note not found" description="The requested note could not be found in this Treeseed." currentPath="/notes/" />
26
27
  ) : (
27
- <NoteLayout note={note.data}>
28
+ <AuthoredEntryLayout
29
+ entry={note.data}
30
+ currentPath="/notes/"
31
+ contributor={null}
32
+ contentTypeLabel="Note"
33
+ contentId={note.id}
34
+ collectionLabel="notes"
35
+ metaLabel="Author"
36
+ metaValue={String(note.data.author ?? metadata?.author ?? '')}
37
+ metadataItems={[
38
+ { label: 'Route', value: note.data.canonicalRoute ?? metadata?.canonicalRoute, href: note.data.canonicalRoute ?? metadata?.canonicalRoute },
39
+ ]}
40
+ >
28
41
  {publishedRuntime ? <PublishedContentBody html={publishedNote?.html ?? ''} /> : <Content />}
29
- </NoteLayout>
42
+ </AuthoredEntryLayout>
30
43
  )
31
44
  }
@@ -45,8 +45,12 @@ const relatedBooks = publishedRuntime
45
45
  entry={objective.data}
46
46
  currentPath="/objectives/"
47
47
  contributor={contributor}
48
+ contentTypeLabel="Objective"
49
+ contentId={objective.id}
50
+ collectionLabel="objectives"
48
51
  metaLabel="Time horizon"
49
52
  metaValue={String(objective.data.timeHorizon ?? '')}
53
+ metadataItems={[]}
50
54
  relatedQuestions={relatedQuestions}
51
55
  relatedBooks={relatedBooks}
52
56
  >
@@ -57,8 +57,14 @@ const supersededProposals = publishedRuntime
57
57
  entry={proposal.data}
58
58
  currentPath="/proposals/"
59
59
  contributor={contributor}
60
+ contentTypeLabel="Proposal"
61
+ contentId={proposal.id}
62
+ collectionLabel="proposals"
60
63
  metaLabel="Proposal type"
61
64
  metaValue={String(proposal.data.proposalType ?? '')}
65
+ metadataItems={[
66
+ { label: 'Route', value: proposal.data.canonicalRoute, href: proposal.data.canonicalRoute },
67
+ ]}
62
68
  relatedObjectives={relatedObjectives}
63
69
  relatedQuestions={relatedQuestions}
64
70
  relatedNotes={relatedNotes}
@@ -45,8 +45,12 @@ const relatedBooks = publishedRuntime
45
45
  entry={question.data}
46
46
  currentPath="/questions/"
47
47
  contributor={contributor}
48
+ contentTypeLabel="Question"
49
+ contentId={question.id}
50
+ collectionLabel="questions"
48
51
  metaLabel="Question type"
49
52
  metaValue={String(question.data.questionType ?? '')}
53
+ metadataItems={[]}
50
54
  relatedObjectives={relatedObjectives}
51
55
  relatedBooks={relatedBooks}
52
56
  >
@@ -391,7 +391,7 @@ async function main() {
391
391
  writeCompatibilityEntrypoint(resolve(distRoot, 'config.d.ts'), "export declare function createTreeseedTenantSite(manifestPath?: string): import('astro').AstroUserConfig<never, never, never>;");
392
392
  writeCompatibilityEntrypoint(resolve(distRoot, 'content.d.ts'), "export declare function createTreeseedCollections(tenantConfig: any, dependencies: any): Record<string, any>;");
393
393
  writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.js'), "import { loadTreeseedManifest } from '@treeseed/sdk/platform/tenant-config';\nimport { docsLoader } from './vendor/starlight/loaders.js';\nimport { docsSchema } from './vendor/starlight/schema.js';\nimport { createTreeseedCollections } from './content.js';\n\nexport function createTreeseedTenantCollections(manifestPath) {\n\tconst tenant = loadTreeseedManifest(manifestPath);\n\treturn createTreeseedCollections(tenant, { docsLoader, docsSchema });\n}");
394
- writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.d.ts'), "export declare function createTreeseedTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};");
394
+ writeCompatibilityEntrypoint(resolve(distRoot, 'content-config.d.ts'), "export declare function createTreeseedTenantCollections(manifestPath?: string): {\n\tpages: any;\n\tnotes: any;\n\tquestions: any;\n\tobjectives: any;\n\tpeople: any;\n\tagents: any;\n\tagent_tests?: any;\n\tbooks: any;\n\tdocs: any;\n\tworkdays?: any;\n};");
395
395
  writeCompatibilityEntrypoint(resolve(distRoot, 'utils/forms/service.d.ts'), "import type { APIContext } from 'astro';\nimport type { SubmitResult } from '../../types/forms.js';\nexport declare function handleTokenRequest(context: APIContext): Promise<Response>;\nexport declare function handleFormSubmission(context: APIContext): Promise<SubmitResult>;");
396
396
  rmSync(resolve(distRoot, 'config.d.js'), { force: true });
397
397
  rmSync(resolve(distRoot, 'content-config.d.js'), { force: true });
@@ -50,6 +50,26 @@ function parseOpenMode(value) {
50
50
  }
51
51
  return undefined;
52
52
  }
53
+ function parseLocalRuntimeMode(value) {
54
+ if (value === 'auto' || value === 'provider' || value === 'local') {
55
+ return value;
56
+ }
57
+ return undefined;
58
+ }
59
+ function readForwardedEnvironment() {
60
+ const keys = [
61
+ 'TREESEED_DOCS_AUTOMATION_MODE',
62
+ 'TREESEED_WORKDAY_ID',
63
+ 'TREESEED_CAPACITY_BUDGET',
64
+ 'TREESEED_WORKDAY_TASK_CREDIT_BUDGET',
65
+ 'TREESEED_APPROVAL_POLICY',
66
+ 'TREESEED_MANAGER_CONSOLE_SUMMARY',
67
+ 'TREESEED_WORKER_CONSOLE_SUMMARY',
68
+ ];
69
+ return Object.fromEntries(keys
70
+ .map((key) => [key, process.env[key]])
71
+ .filter((entry) => typeof entry[1] === 'string' && entry[1].length > 0));
72
+ }
53
73
  const exitCode = await runTreeseedIntegratedDev({
54
74
  surface: parseSurface(readOption('--surface')),
55
75
  surfaces: readOption('--surfaces'),
@@ -58,13 +78,16 @@ const exitCode = await runTreeseedIntegratedDev({
58
78
  webPort: readNumberOption('--port'),
59
79
  apiHost: readOption('--api-host'),
60
80
  apiPort: readNumberOption('--api-port'),
81
+ webRuntime: parseLocalRuntimeMode(readOption('--web-runtime')),
61
82
  setupMode: parseSetupMode(readOption('--setup')),
62
83
  feedbackMode: parseFeedbackMode(readOption('--feedback')),
63
84
  openMode: parseOpenMode(readOption('--open')),
64
85
  plan: readFlag('--plan'),
65
86
  reset: readFlag('--reset'),
87
+ force: readFlag('--force'),
66
88
  json: readFlag('--json'),
67
89
  projectId: readOption('--project-id'),
68
90
  teamId: readOption('--team-id'),
91
+ env: readForwardedEnvironment(),
69
92
  });
70
93
  process.exit(exitCode);
@@ -15,9 +15,13 @@ if (process.env.GITHUB_ACTIONS === 'true') {
15
15
  npmArgs.push(...extraArgs);
16
16
  const result = spawnSync('npm', npmArgs, {
17
17
  cwd: packageRoot,
18
- stdio: 'inherit',
18
+ encoding: 'utf8',
19
19
  env: process.env,
20
20
  });
21
+ if (result.stdout)
22
+ process.stdout.write(result.stdout);
23
+ if (result.stderr)
24
+ process.stderr.write(result.stderr);
21
25
  if (result.error) {
22
26
  console.error(result.error.message);
23
27
  process.exit(1);
package/dist/site.js CHANGED
@@ -313,6 +313,9 @@ function createTreeseedSite(tenantConfig, { starlight }) {
313
313
  __TREESEED_DEPLOY_CONFIG__: injectedDeployConfig,
314
314
  __TREESEED_BOOK_RUNTIME__: injectedBookRuntime
315
315
  },
316
+ optimizeDeps: {
317
+ exclude: ["libsodium-wrappers-sumo"]
318
+ },
316
319
  plugins: [
317
320
  createTenantThemeVitePlugin(tenantThemeCss),
318
321
  tailwindcss(),
@@ -27,15 +27,25 @@
27
27
  align-self: start;
28
28
  background: var(--ts-color-canvas-subtle);
29
29
  border-right: 1px solid var(--ts-color-border);
30
+ box-sizing: border-box;
30
31
  display: grid;
31
32
  gap: var(--ts-space-3);
33
+ grid-template-rows: minmax(0, 1fr) auto;
32
34
  height: 100vh;
33
- overflow: auto;
35
+ overflow: hidden;
34
36
  padding: var(--ts-space-3);
35
37
  position: sticky;
36
38
  top: 0;
37
39
  }
38
40
 
41
+ .ts-app-shell__rail-scroll {
42
+ display: grid;
43
+ gap: var(--ts-space-3);
44
+ min-height: 0;
45
+ overflow: auto;
46
+ padding-right: 0.15rem;
47
+ }
48
+
39
49
  .ts-shell-brand {
40
50
  align-items: center;
41
51
  color: var(--ts-color-text);
@@ -109,6 +119,12 @@
109
119
  min-width: 0;
110
120
  }
111
121
 
122
+ .ts-shell-utility-actions {
123
+ align-items: center;
124
+ display: inline-flex;
125
+ gap: var(--ts-space-1);
126
+ }
127
+
112
128
  .ts-app-shell__rail-context,
113
129
  .ts-app-shell__quick-actions {
114
130
  border-top: 1px solid var(--ts-color-border);
@@ -117,6 +133,11 @@
117
133
  padding-top: var(--ts-space-2);
118
134
  }
119
135
 
136
+ .ts-app-shell__quick-actions {
137
+ background: var(--ts-color-canvas-subtle);
138
+ padding-bottom: 0.1rem;
139
+ }
140
+
120
141
  .ts-app-shell__eyebrow {
121
142
  color: var(--ts-color-text-subtle);
122
143
  font-size: 0.75rem;
@@ -568,6 +589,10 @@
568
589
  width: 100%;
569
590
  }
570
591
 
592
+ .ts-app-shell__header-actions .ts-shell-utility-actions {
593
+ display: none;
594
+ }
595
+
571
596
  .ts-bottom-nav {
572
597
  display: grid;
573
598
  }
@@ -20,13 +20,13 @@ body {
20
20
 
21
21
  .ts-theme-menu__trigger {
22
22
  align-items: center;
23
- background: var(--ts-color-surface);
24
- border: 1px solid var(--ts-color-border);
23
+ background: transparent;
24
+ border: 1px solid transparent;
25
25
  border-radius: var(--ts-radius-md);
26
26
  color: var(--ts-color-text-muted);
27
27
  cursor: pointer;
28
28
  display: inline-flex;
29
- height: 2rem;
29
+ height: 2.25rem;
30
30
  justify-content: center;
31
31
  list-style: none;
32
32
  padding: 0;
@@ -34,7 +34,7 @@ body {
34
34
  background-color 140ms ease,
35
35
  border-color 140ms ease,
36
36
  color 140ms ease;
37
- width: 2rem;
37
+ width: 2.25rem;
38
38
  }
39
39
 
40
40
  .ts-theme-menu__trigger::-webkit-details-marker {
@@ -43,8 +43,8 @@ body {
43
43
 
44
44
  .ts-theme-menu__trigger:hover,
45
45
  .ts-theme-menu[open] .ts-theme-menu__trigger {
46
- background: var(--ts-color-surface-muted);
47
- border-color: var(--ts-color-border-strong);
46
+ background: var(--ts-color-surface-raised);
47
+ border-color: var(--ts-color-border);
48
48
  color: var(--ts-color-text);
49
49
  }
50
50
 
@@ -56,8 +56,8 @@ body {
56
56
  .ts-theme-menu__trigger svg {
57
57
  display: block;
58
58
  fill: currentColor;
59
- height: 1rem;
60
- width: 1rem;
59
+ height: 1.15rem;
60
+ width: 1.15rem;
61
61
  }
62
62
 
63
63
  .ts-theme-menu__panel {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/core",
3
- "version": "0.8.19",
3
+ "version": "0.9.4",
4
4
  "description": "Treeseed web framework package for Astro/Starlight site runtimes.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -70,7 +70,7 @@
70
70
  "@astrojs/sitemap": "3.7.0",
71
71
  "@astrojs/starlight": "0.37.6",
72
72
  "@tailwindcss/vite": "^4.1.4",
73
- "@treeseed/sdk": "0.8.19",
73
+ "@treeseed/sdk": "github:treeseed-ai/sdk#0.10.6",
74
74
  "astro": "^5.6.1",
75
75
  "esbuild": "^0.28.0",
76
76
  "katex": "^0.16.22",