@treeseed/core 0.4.9 → 0.4.11

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.
Files changed (82) hide show
  1. package/README.md +1 -2
  2. package/dist/agent.d.ts +0 -1
  3. package/dist/agent.js +0 -2
  4. package/dist/agents/spec-types.d.ts +10 -10
  5. package/dist/api/agent-routes.d.ts +2 -2
  6. package/dist/api/agent-routes.js +51 -125
  7. package/dist/api/app.js +56 -4
  8. package/dist/api/auth/d1-store.d.ts +1 -0
  9. package/dist/api/auth/d1-store.js +21 -1
  10. package/dist/api/auth/rbac.d.ts +2 -2
  11. package/dist/api/auth/rbac.js +2 -1
  12. package/dist/api/config.js +4 -0
  13. package/dist/api/http.d.ts +4 -0
  14. package/dist/api/http.js +7 -0
  15. package/dist/api/index.d.ts +1 -1
  16. package/dist/api/index.js +2 -2
  17. package/dist/api/operations-routes.d.ts +1 -0
  18. package/dist/api/operations-routes.js +6 -1
  19. package/dist/api/railway.d.ts +4 -0
  20. package/dist/api/sdk-dispatch.d.ts +2 -11
  21. package/dist/api/sdk-dispatch.js +1 -133
  22. package/dist/api/sdk-routes.d.ts +1 -0
  23. package/dist/api/sdk-routes.js +5 -1
  24. package/dist/api/types.d.ts +32 -16
  25. package/dist/components/site/RouteNotFound.astro +25 -0
  26. package/dist/content-config.d.ts +1 -0
  27. package/dist/content.d.ts +1 -0
  28. package/dist/content.js +177 -1
  29. package/dist/dev.d.ts +7 -2
  30. package/dist/dev.js +83 -2
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +9 -3
  33. package/dist/middleware/editorial-preview.d.ts +26 -0
  34. package/dist/middleware/editorial-preview.js +37 -0
  35. package/dist/middleware/starlightRouteData.js +15 -4
  36. package/dist/pages/[slug].astro +12 -10
  37. package/dist/pages/agents/[slug].astro +28 -21
  38. package/dist/pages/books/[slug].astro +19 -12
  39. package/dist/pages/feed.xml.js +6 -4
  40. package/dist/pages/index.astro +43 -14
  41. package/dist/pages/notes/[slug].astro +19 -12
  42. package/dist/pages/objectives/[slug].astro +30 -23
  43. package/dist/pages/people/[slug].astro +28 -21
  44. package/dist/pages/questions/[slug].astro +30 -23
  45. package/dist/scripts/build-dist.js +6 -1
  46. package/dist/scripts/dev-platform.js +9 -1
  47. package/dist/scripts/test-smoke.js +0 -1
  48. package/dist/services/agents.d.ts +22 -0
  49. package/dist/services/agents.js +29 -0
  50. package/dist/services/common.d.ts +37 -4
  51. package/dist/services/common.js +135 -17
  52. package/dist/services/index.d.ts +4 -1
  53. package/dist/services/index.js +14 -2
  54. package/dist/services/manager.d.ts +246 -3
  55. package/dist/services/manager.js +1101 -171
  56. package/dist/services/remote-runner.d.ts +30 -0
  57. package/dist/services/remote-runner.js +111 -0
  58. package/dist/services/workday-content.d.ts +53 -0
  59. package/dist/services/workday-content.js +190 -0
  60. package/dist/services/workday-report.d.ts +160 -2
  61. package/dist/services/workday-report.js +4 -31
  62. package/dist/services/workday-start.d.ts +174 -1
  63. package/dist/services/workday-start.js +3 -13
  64. package/dist/services/worker-pool-scaler.d.ts +27 -0
  65. package/dist/services/worker-pool-scaler.js +109 -0
  66. package/dist/services/worker.d.ts +7 -0
  67. package/dist/services/worker.js +41 -57
  68. package/dist/site.js +43 -27
  69. package/dist/templates.d.ts +98 -0
  70. package/dist/templates.js +170 -0
  71. package/dist/tenant/runtime-config.d.ts +4 -0
  72. package/dist/tenant/runtime-config.js +34 -1
  73. package/dist/utils/hub-content.js +35 -0
  74. package/dist/utils/published-content.js +60 -0
  75. package/dist/utils/site-models.d.ts +6 -0
  76. package/dist/utils/site-models.js +16 -0
  77. package/dist/utils/starlight-nav.js +50 -0
  78. package/package.json +23 -9
  79. package/templates/github/deploy.workflow.yml +404 -9
  80. package/templates/github/hosted-project.workflow.yml +77 -0
  81. package/dist/api/gateway.d.ts +0 -5
  82. package/dist/api/gateway.js +0 -35
package/dist/dev.js CHANGED
@@ -10,6 +10,7 @@ const TREESEED_DEFAULT_WEB_HOST = "127.0.0.1";
10
10
  const TREESEED_DEFAULT_WEB_PORT = 4321;
11
11
  const TREESEED_DEFAULT_API_HOST = "127.0.0.1";
12
12
  const TREESEED_DEFAULT_API_PORT = 3e3;
13
+ const TREESEED_DEFAULT_MANAGER_PORT = 3100;
13
14
  function resolvePackageRoot(packageName, tenantRoot) {
14
15
  const resolvedPath = require2.resolve(packageName, {
15
16
  paths: [tenantRoot, packageRoot, process.cwd()]
@@ -38,6 +39,28 @@ function resolveNodeEntrypoint(packageDir, sourceRelativePath, distRelativePath)
38
39
  args: [resolve(packageDir, distRelativePath)]
39
40
  };
40
41
  }
42
+ function resolveTenantApiEntrypoint(tenantRoot, runTsPath) {
43
+ const javascriptCandidates = [
44
+ resolve(tenantRoot, "src", "api", "server.js"),
45
+ resolve(tenantRoot, "src", "api", "server.mjs")
46
+ ];
47
+ for (const candidate of javascriptCandidates) {
48
+ if (existsSync(candidate)) {
49
+ return {
50
+ command: process.execPath,
51
+ args: [candidate]
52
+ };
53
+ }
54
+ }
55
+ const typescriptCandidate = resolve(tenantRoot, "src", "api", "server.ts");
56
+ if (existsSync(typescriptCandidate) && existsSync(runTsPath)) {
57
+ return {
58
+ command: process.execPath,
59
+ args: [runTsPath, typescriptCandidate]
60
+ };
61
+ }
62
+ return null;
63
+ }
41
64
  function withWatchArgs(args, watchPaths) {
42
65
  return watchPaths.flatMap((watchPath) => ["--watch-path", watchPath]).concat(args);
43
66
  }
@@ -52,19 +75,39 @@ function createTreeseedIntegratedDevPlan(options = {}) {
52
75
  const webPort = normalizePort(options.webPort, TREESEED_DEFAULT_WEB_PORT);
53
76
  const apiHost = options.apiHost ?? TREESEED_DEFAULT_API_HOST;
54
77
  const apiPort = normalizePort(options.apiPort, TREESEED_DEFAULT_API_PORT);
78
+ const managerPort = normalizePort(options.managerPort, TREESEED_DEFAULT_MANAGER_PORT);
79
+ const includeServices = options.includeServices ?? (surface === "integrated" || surface === "services");
80
+ const projectId = options.projectId ?? process.env.TREESEED_PROJECT_ID;
81
+ const teamId = options.teamId ?? process.env.TREESEED_HOSTING_TEAM_ID;
55
82
  const mergedEnv = { ...process.env, ...options.env ?? {} };
56
83
  const apiBaseUrl = mergedEnv.TREESEED_API_BASE_URL?.trim() || `http://${apiHost}:${apiPort}`;
57
84
  const sdkPackageRoot = resolvePackageRoot("@treeseed/sdk", tenantRoot);
85
+ const coreRunTsPath = resolve(packageRoot, "scripts", "run-ts.mjs");
58
86
  const webEntrypoint = resolveNodeEntrypoint(
59
87
  sdkPackageRoot,
60
88
  "scripts/tenant-astro-command.ts",
61
89
  "dist/scripts/tenant-astro-command.js"
62
90
  );
63
- const apiEntrypoint = resolveNodeEntrypoint(
91
+ const apiEntrypoint = resolveTenantApiEntrypoint(tenantRoot, coreRunTsPath) ?? resolveNodeEntrypoint(
64
92
  packageRoot,
65
93
  "src/api/server.ts",
66
94
  "dist/api/server.js"
67
95
  );
96
+ const managerEntrypoint = resolveNodeEntrypoint(
97
+ packageRoot,
98
+ "src/services/manager.ts",
99
+ "dist/services/manager.js"
100
+ );
101
+ const workerEntrypoint = resolveNodeEntrypoint(
102
+ packageRoot,
103
+ "src/services/worker.ts",
104
+ "dist/services/worker.js"
105
+ );
106
+ const agentsEntrypoint = resolveNodeEntrypoint(
107
+ packageRoot,
108
+ "src/services/agents.ts",
109
+ "dist/services/agents.js"
110
+ );
68
111
  const watchPaths = [
69
112
  resolve(packageRoot, existsSync(resolve(packageRoot, "src")) ? "src" : "dist"),
70
113
  resolve(tenantRoot, "src"),
@@ -74,7 +117,10 @@ function createTreeseedIntegratedDevPlan(options = {}) {
74
117
  const sharedEnv = {
75
118
  ...mergedEnv,
76
119
  TREESEED_LOCAL_DEV_MODE: mergedEnv.TREESEED_LOCAL_DEV_MODE ?? "cloudflare",
77
- TREESEED_API_BASE_URL: apiBaseUrl
120
+ TREESEED_API_BASE_URL: apiBaseUrl,
121
+ TREESEED_MARKET_API_BASE_URL: mergedEnv.TREESEED_MARKET_API_BASE_URL ?? apiBaseUrl,
122
+ TREESEED_PROJECT_ID: projectId ?? mergedEnv.TREESEED_PROJECT_ID,
123
+ TREESEED_HOSTING_TEAM_ID: teamId ?? mergedEnv.TREESEED_HOSTING_TEAM_ID
78
124
  };
79
125
  if (watch) {
80
126
  sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD = sharedEnv.TREESEED_PUBLIC_DEV_WATCH_RELOAD || "true";
@@ -103,6 +149,40 @@ function createTreeseedIntegratedDevPlan(options = {}) {
103
149
  }
104
150
  });
105
151
  }
152
+ if (includeServices || surface === "manager") {
153
+ commands.push({
154
+ id: "manager",
155
+ label: "Manager",
156
+ command: managerEntrypoint.command,
157
+ args: watch ? withWatchArgs(managerEntrypoint.args, watchPaths) : managerEntrypoint.args,
158
+ cwd: tenantRoot,
159
+ env: {
160
+ ...sharedEnv,
161
+ PORT: sharedEnv.PORT ?? String(managerPort),
162
+ TREESEED_MANAGER_BASE_URL: sharedEnv.TREESEED_MANAGER_BASE_URL ?? `http://${apiHost}:${managerPort}`
163
+ }
164
+ });
165
+ }
166
+ if (includeServices || surface === "worker") {
167
+ commands.push({
168
+ id: "worker",
169
+ label: "Worker",
170
+ command: workerEntrypoint.command,
171
+ args: watch ? withWatchArgs(workerEntrypoint.args, watchPaths) : workerEntrypoint.args,
172
+ cwd: tenantRoot,
173
+ env: sharedEnv
174
+ });
175
+ }
176
+ if (includeServices || surface === "agents") {
177
+ commands.push({
178
+ id: "agents",
179
+ label: "Agents",
180
+ command: agentsEntrypoint.command,
181
+ args: watch ? withWatchArgs(agentsEntrypoint.args, watchPaths) : agentsEntrypoint.args,
182
+ cwd: tenantRoot,
183
+ env: sharedEnv
184
+ });
185
+ }
106
186
  return {
107
187
  surface,
108
188
  tenantRoot,
@@ -182,6 +262,7 @@ async function runTreeseedIntegratedDev(options = {}, deps = {}) {
182
262
  export {
183
263
  TREESEED_DEFAULT_API_HOST,
184
264
  TREESEED_DEFAULT_API_PORT,
265
+ TREESEED_DEFAULT_MANAGER_PORT,
185
266
  TREESEED_DEFAULT_WEB_HOST,
186
267
  TREESEED_DEFAULT_WEB_PORT,
187
268
  createTreeseedIntegratedDevPlan,
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { buildTreeseedSiteLayers, resolveTreeseedPageEntrypoint, resolveTreeseed
2
2
  export { buildTreeseedPlatformLayers, resolveTreeseedPlatformResource, TREESEED_PLATFORM_RESOURCE_KINDS, } from './platform-resources';
3
3
  export { parseSiteConfig } from './utils/site-config-schema.js';
4
4
  export { createTreeseedApiApp } from './api/app';
5
- export { createTreeseedGatewayApp } from './api/gateway';
6
5
  export { createRailwayTreeseedApiServer } from './api/railway';
7
6
  export { resolveApiConfig } from './api/config';
8
7
  export { createTreeseedIntegratedDevPlan, runTreeseedIntegratedDev, type TreeseedIntegratedDevCommand, type TreeseedIntegratedDevOptions, type TreeseedIntegratedDevPlan, type TreeseedIntegratedDevSurface, } from './dev';
8
+ export { filterSiteRenderedModels, isSiteRenderedModel, siteModelRendered, } from './utils/site-models.ts';
9
9
  export type * from './api/types';
package/dist/index.js CHANGED
@@ -12,13 +12,17 @@ import {
12
12
  } from "./platform-resources.js";
13
13
  import { parseSiteConfig } from "./utils/site-config-schema.js";
14
14
  import { createTreeseedApiApp } from "./api/app.js";
15
- import { createTreeseedGatewayApp } from "./api/gateway.js";
16
15
  import { createRailwayTreeseedApiServer } from "./api/railway.js";
17
16
  import { resolveApiConfig } from "./api/config.js";
18
17
  import {
19
18
  createTreeseedIntegratedDevPlan,
20
19
  runTreeseedIntegratedDev
21
20
  } from "./dev.js";
21
+ import {
22
+ filterSiteRenderedModels,
23
+ isSiteRenderedModel,
24
+ siteModelRendered
25
+ } from "./utils/site-models.js";
22
26
  export {
23
27
  TREESEED_PLATFORM_RESOURCE_KINDS,
24
28
  TREESEED_SITE_RESOURCE_KINDS,
@@ -26,13 +30,15 @@ export {
26
30
  buildTreeseedSiteLayers,
27
31
  createRailwayTreeseedApiServer,
28
32
  createTreeseedApiApp,
29
- createTreeseedGatewayApp,
30
33
  createTreeseedIntegratedDevPlan,
34
+ filterSiteRenderedModels,
35
+ isSiteRenderedModel,
31
36
  parseSiteConfig,
32
37
  resolveApiConfig,
33
38
  resolveTreeseedPageEntrypoint,
34
39
  resolveTreeseedPlatformResource,
35
40
  resolveTreeseedSiteResource,
36
41
  resolveTreeseedStyleEntrypoint,
37
- runTreeseedIntegratedDev
42
+ runTreeseedIntegratedDev,
43
+ siteModelRendered
38
44
  };
@@ -0,0 +1,26 @@
1
+ import { type EditorialPreviewTokenPayload } from '@treeseed/sdk/platform/published-content';
2
+ type CookieValue = {
3
+ value: string;
4
+ } | undefined;
5
+ export interface EditorialPreviewContextLike {
6
+ url: URL;
7
+ cookies: {
8
+ get(name: string): CookieValue;
9
+ set(name: string, value: string, options?: {
10
+ httpOnly?: boolean;
11
+ path?: string;
12
+ sameSite?: 'lax' | 'strict' | 'none';
13
+ secure?: boolean;
14
+ expires?: Date;
15
+ }): void;
16
+ delete(name: string, options?: {
17
+ path?: string;
18
+ }): void;
19
+ };
20
+ locals: object;
21
+ }
22
+ export declare function resolveEditorialPreview(context: EditorialPreviewContextLike, options?: {
23
+ secret?: string | null;
24
+ cookieName?: string;
25
+ }): EditorialPreviewTokenPayload | null;
26
+ export {};
@@ -0,0 +1,37 @@
1
+ import {
2
+ EDITORIAL_PREVIEW_COOKIE,
3
+ verifyEditorialPreviewToken
4
+ } from "@treeseed/sdk/platform/published-content";
5
+ function previewSecretFromLocals(locals) {
6
+ const runtime = locals?.runtime;
7
+ return typeof runtime?.env?.TREESEED_EDITORIAL_PREVIEW_SECRET === "string" ? runtime.env.TREESEED_EDITORIAL_PREVIEW_SECRET : "";
8
+ }
9
+ function resolveEditorialPreview(context, options = {}) {
10
+ const cookieName = options.cookieName ?? EDITORIAL_PREVIEW_COOKIE;
11
+ const secret = typeof options.secret === "string" ? options.secret : previewSecretFromLocals(context.locals);
12
+ const queryPreview = context.url.searchParams.get("preview");
13
+ if (queryPreview === "clear") {
14
+ context.cookies.delete(cookieName, { path: "/" });
15
+ context.locals.contentPreview = null;
16
+ return null;
17
+ }
18
+ const cookiePreview = context.cookies.get(cookieName)?.value ?? null;
19
+ const activeToken = queryPreview || cookiePreview;
20
+ const resolvedPreview = activeToken && secret ? verifyEditorialPreviewToken(activeToken, secret) : null;
21
+ if (queryPreview && resolvedPreview) {
22
+ context.cookies.set(cookieName, queryPreview, {
23
+ httpOnly: true,
24
+ path: "/",
25
+ sameSite: "lax",
26
+ secure: context.url.protocol === "https:",
27
+ expires: new Date(resolvedPreview.expiresAt)
28
+ });
29
+ } else if (activeToken && !resolvedPreview) {
30
+ context.cookies.delete(cookieName, { path: "/" });
31
+ }
32
+ context.locals.contentPreview = resolvedPreview;
33
+ return resolvedPreview;
34
+ }
35
+ export {
36
+ resolveEditorialPreview
37
+ };
@@ -1,5 +1,8 @@
1
1
  import { defineRouteMiddleware } from "../vendor/starlight/route-data.js";
2
- import { BOOKS, TREESEED_LINKS, normalizeHref } from "../utils/starlight-nav.js";
2
+ import { TREESEED_LINKS, buildStarlightSidebarEntriesFromRuntime, normalizeHref } from "../utils/starlight-nav.js";
3
+ import { loadHostedBookRuntime } from "../utils/published-content.js";
4
+ import { BOOKS, BOOKS_LINK, TREESEED_LIBRARY_DOWNLOAD } from "@treeseed/sdk/platform/books-data";
5
+ import { loadTreeseedManifest, tenantModelRendered } from "@treeseed/sdk/platform/tenant-config";
3
6
  const copyLink = (entry) => ({ ...entry, attrs: { ...entry.attrs } });
4
7
  const copyEntry = (entry) => entry.type === "link" ? copyLink(entry) : {
5
8
  ...entry,
@@ -23,10 +26,18 @@ const setRouteSidebar = (route, currentPath, sidebar, paginationSource) => {
23
26
  route.hasSidebar = sidebar.length > 0;
24
27
  route.pagination = paginationSource ? buildPagination(paginationSource, currentPath) : { prev: void 0, next: void 0 };
25
28
  };
26
- const onRequest = defineRouteMiddleware((context) => {
29
+ const defaultRuntime = { BOOKS, BOOKS_LINK, TREESEED_LIBRARY_DOWNLOAD, TREESEED_LINKS };
30
+ const tenantConfig = loadTreeseedManifest();
31
+ const onRequest = defineRouteMiddleware(async (context) => {
27
32
  const route = context.locals.starlightRoute;
28
33
  const currentPath = normalizeHref(context.url.pathname);
29
- const activeBook = BOOKS.find(
34
+ if (!tenantModelRendered(tenantConfig, "books")) {
35
+ setRouteSidebar(route, currentPath, [], null);
36
+ return;
37
+ }
38
+ const runtime = await loadHostedBookRuntime(context.locals) ?? defaultRuntime;
39
+ route.sidebar = buildStarlightSidebarEntriesFromRuntime(runtime, currentPath);
40
+ const activeBook = runtime.BOOKS.find(
30
41
  (book) => currentPath.startsWith(normalizeHref(book.basePath))
31
42
  );
32
43
  if (activeBook) {
@@ -35,7 +46,7 @@ const onRequest = defineRouteMiddleware((context) => {
35
46
  setRouteSidebar(route, currentPath, [copyEntry(bookGroup)], bookGroup.entries);
36
47
  return;
37
48
  }
38
- if (currentPath === normalizeHref(TREESEED_LINKS.home)) {
49
+ if (currentPath === normalizeHref(runtime.TREESEED_LINKS.home)) {
39
50
  setRouteSidebar(route, currentPath, [], null);
40
51
  return;
41
52
  }
@@ -2,23 +2,25 @@
2
2
  import { getCollection, render } from 'astro:content';
3
3
  import ContentLayout from '../layouts/ContentLayout.astro';
4
4
  import BridgeLayout from '../layouts/BridgeLayout.astro';
5
+ import RouteNotFound from '../components/site/RouteNotFound.astro';
5
6
 
6
- export async function getStaticPaths() {
7
- const entries = await getCollection('pages');
7
+ export const prerender = false;
8
8
 
9
- return entries.map((entry) => ({
10
- params: { slug: entry.data.slug },
11
- props: { entry },
12
- }));
9
+ const slug = String(Astro.params.slug ?? '');
10
+ const entries = await getCollection('pages');
11
+ const entry = entries.find((candidate) => candidate.data.slug === slug) ?? null;
12
+ if (!entry) {
13
+ Astro.response.status = 404;
13
14
  }
14
-
15
- const { entry } = Astro.props;
16
- const { Content } = await render(entry);
15
+ const rendered = entry ? await render(entry) : null;
16
+ const Content = rendered?.Content ?? null;
17
17
  const currentPath = Astro.url.pathname;
18
18
  ---
19
19
 
20
20
  {
21
- entry.data.pageLayout === 'bridge' ? (
21
+ !entry || !Content ? (
22
+ <RouteNotFound title="Page not found" description="The requested page is not available in this Treeseed." currentPath={currentPath} />
23
+ ) : entry.data.pageLayout === 'bridge' ? (
22
24
  <BridgeLayout entry={entry.data} currentPath={currentPath}>
23
25
  <Content />
24
26
  </BridgeLayout>
@@ -2,28 +2,35 @@
2
2
  import { getCollection, render } from 'astro:content';
3
3
  import ProfileLayout from '../../layouts/ProfileLayout.astro';
4
4
  import { resolveReferences } from '../../utils/hub-content';
5
+ import RouteNotFound from '../../components/site/RouteNotFound.astro';
5
6
 
6
- export async function getStaticPaths() {
7
- const agents = await getCollection('agents');
8
- return agents.map((agent) => ({
9
- params: { slug: agent.id },
10
- props: { agent },
11
- }));
12
- }
7
+ export const prerender = false;
13
8
 
14
- const { agent } = Astro.props;
15
- const { Content } = await render(agent);
16
- const relatedQuestions = await resolveReferences(agent.data.relatedQuestions);
17
- const relatedObjectives = await resolveReferences(agent.data.relatedObjectives);
9
+ const slug = String(Astro.params.slug ?? '');
10
+ const agents = await getCollection('agents');
11
+ const agent = agents.find((candidate) => candidate.id === slug) ?? null;
12
+ if (!agent) {
13
+ Astro.response.status = 404;
14
+ }
15
+ const rendered = agent ? await render(agent) : null;
16
+ const Content = rendered?.Content ?? null;
17
+ const relatedQuestions = agent ? await resolveReferences(agent.data.relatedQuestions) : [];
18
+ const relatedObjectives = agent ? await resolveReferences(agent.data.relatedObjectives) : [];
18
19
  ---
19
20
 
20
- <ProfileLayout
21
- entry={agent.data}
22
- currentPath="/agents/"
23
- metaLabel="Operator"
24
- metaValue={`${agent.data.operator} · ${agent.data.runtimeStatus}`}
25
- relatedQuestions={relatedQuestions}
26
- relatedObjectives={relatedObjectives}
27
- >
28
- <Content />
29
- </ProfileLayout>
21
+ {
22
+ !agent || !Content ? (
23
+ <RouteNotFound title="Agent not found" description="The requested agent profile could not be found in this Treeseed." currentPath="/agents/" />
24
+ ) : (
25
+ <ProfileLayout
26
+ entry={agent.data}
27
+ currentPath="/agents/"
28
+ metaLabel="Operator"
29
+ metaValue={`${agent.data.operator} · ${agent.data.runtimeStatus}`}
30
+ relatedQuestions={relatedQuestions}
31
+ relatedObjectives={relatedObjectives}
32
+ >
33
+ <Content />
34
+ </ProfileLayout>
35
+ )
36
+ }
@@ -1,19 +1,26 @@
1
1
  ---
2
2
  import { getCollection, render } from 'astro:content';
3
3
  import BookLayout from '../../layouts/BookLayout.astro';
4
+ import RouteNotFound from '../../components/site/RouteNotFound.astro';
4
5
 
5
- export async function getStaticPaths() {
6
- const books = (await getCollection('books')).sort((a, b) => a.data.order - b.data.order);
7
- return books.map((book) => ({
8
- params: { slug: book.id },
9
- props: { book },
10
- }));
11
- }
6
+ export const prerender = false;
12
7
 
13
- const { book } = Astro.props;
14
- const { Content } = await render(book);
8
+ const slug = String(Astro.params.slug ?? '');
9
+ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.data.order);
10
+ const book = books.find((candidate) => candidate.id === slug) ?? null;
11
+ if (!book) {
12
+ Astro.response.status = 404;
13
+ }
14
+ const rendered = book ? await render(book) : null;
15
+ const Content = rendered?.Content ?? null;
15
16
  ---
16
17
 
17
- <BookLayout entry={book.data} currentPath="/books/">
18
- <Content />
19
- </BookLayout>
18
+ {
19
+ !book || !Content ? (
20
+ <RouteNotFound title="Book not found" description="The requested book could not be found in this Treeseed." currentPath="/books/" />
21
+ ) : (
22
+ <BookLayout entry={book.data} currentPath="/books/">
23
+ <Content />
24
+ </BookLayout>
25
+ )
26
+ }
@@ -1,8 +1,10 @@
1
- import { getCollection } from "astro:content";
1
+ import { getPublishedNotes } from "../utils/hub-content.js";
2
+ import { siteModelRendered } from "../utils/site-models.js";
2
3
  async function GET(context) {
3
- const notes = (await getCollection("notes", ({ data }) => !data.draft)).sort(
4
- (a, b) => b.data.date.valueOf() - a.data.date.valueOf()
5
- );
4
+ if (!siteModelRendered("notes")) {
5
+ return new Response("Not found", { status: 404 });
6
+ }
7
+ const notes = await getPublishedNotes();
6
8
  const origin = context.site?.origin ?? "https://treeseed.dev";
7
9
  const items = notes.map(
8
10
  (note) => `
@@ -1,5 +1,4 @@
1
1
  ---
2
- import { getCollection } from 'astro:content';
3
2
  import MainLayout from '../layouts/MainLayout.astro';
4
3
  import Hero from '../components/site/Hero.astro';
5
4
  import StageBanner from '../components/site/StageBanner.astro';
@@ -11,18 +10,36 @@ import NotesList from '../components/site/NotesList.astro';
11
10
  import ChronicleList from '../components/site/ChronicleList.astro';
12
11
  import ProfileList from '../components/site/ProfileList.astro';
13
12
  import BookList from '../components/site/BookList.astro';
14
- import { getPublishedObjectives, getPublishedQuestions, resolveContributorsForEntries } from '../utils/hub-content';
13
+ import {
14
+ getPublishedAgents,
15
+ getPublishedBooks,
16
+ getPublishedNotes,
17
+ getPublishedObjectives,
18
+ getPublishedPeople,
19
+ getPublishedQuestions,
20
+ resolveContributorsForEntries,
21
+ } from '../utils/hub-content';
22
+ import { siteModelRendered } from '../utils/site-models.js';
15
23
 
16
- const notes = (await getCollection('notes', ({ data }) => !data.draft))
17
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
18
- .slice(0, 3);
19
- const questions = (await getPublishedQuestions()).slice(0, 2);
20
- const objectives = (await getPublishedObjectives()).slice(0, 2);
24
+ const notesRendered = siteModelRendered('notes');
25
+ const questionsRendered = siteModelRendered('questions');
26
+ const objectivesRendered = siteModelRendered('objectives');
27
+ const peopleRendered = siteModelRendered('people');
28
+ const agentsRendered = siteModelRendered('agents');
29
+ const booksRendered = siteModelRendered('books');
30
+
31
+ const notes = notesRendered ? (await getPublishedNotes()).slice(0, 3) : [];
32
+ const questions = questionsRendered ? (await getPublishedQuestions()).slice(0, 2) : [];
33
+ const objectives = objectivesRendered ? (await getPublishedObjectives()).slice(0, 2) : [];
21
34
  const questionContributors = await resolveContributorsForEntries(questions);
22
35
  const objectiveContributors = await resolveContributorsForEntries(objectives);
23
- const people = (await getCollection('people')).slice(0, 2);
24
- const agents = (await getCollection('agents')).slice(0, 2);
25
- const books = (await getCollection('books')).sort((a, b) => a.data.order - b.data.order);
36
+ const people = peopleRendered ? (await getPublishedPeople()).slice(0, 2) : [];
37
+ const agents = agentsRendered ? (await getPublishedAgents()).slice(0, 2) : [];
38
+ const books = booksRendered ? await getPublishedBooks() : [];
39
+ const ctaPrimaryHref = questionsRendered ? '/questions/' : objectivesRendered ? '/objectives/' : '/status/';
40
+ const ctaPrimaryLabel = questionsRendered ? 'Browse questions' : objectivesRendered ? 'Browse objectives' : 'View status';
41
+ const ctaSecondaryHref = booksRendered ? '/books/' : notesRendered ? '/notes/' : '/vision/';
42
+ const ctaSecondaryLabel = booksRendered ? 'Explore books' : notesRendered ? 'Read notes' : 'Read vision';
26
43
  ---
27
44
 
28
45
  <MainLayout
@@ -178,6 +195,7 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
178
195
  </div>
179
196
  </section>
180
197
 
198
+ {notesRendered && (
181
199
  <section class="space-y-6 border-t border-[color:var(--site-border)] pt-10">
182
200
  <SectionIntro
183
201
  eyebrow="Notes"
@@ -186,7 +204,9 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
186
204
  />
187
205
  <NotesList notes={notes} />
188
206
  </section>
207
+ )}
189
208
 
209
+ {(questionsRendered || objectivesRendered || peopleRendered || agentsRendered || booksRendered) && (
190
210
  <section class="space-y-8 border-t border-[color:var(--site-border)] pt-10">
191
211
  <SectionIntro
192
212
  eyebrow="TreeSeed"
@@ -194,6 +214,7 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
194
214
  description="The fixture shows how humans and agents can publish questions, anchor them to objectives, and connect that work to books and contributor profiles."
195
215
  />
196
216
  <div class="grid gap-10 lg:grid-cols-2">
217
+ {questionsRendered && (
197
218
  <div class="space-y-6">
198
219
  <SectionIntro
199
220
  eyebrow="Questions"
@@ -212,6 +233,8 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
212
233
  }))}
213
234
  />
214
235
  </div>
236
+ )}
237
+ {objectivesRendered && (
215
238
  <div class="space-y-6">
216
239
  <SectionIntro
217
240
  eyebrow="Objectives"
@@ -230,8 +253,10 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
230
253
  }))}
231
254
  />
232
255
  </div>
256
+ )}
233
257
  </div>
234
258
  <div class="grid gap-10 lg:grid-cols-2">
259
+ {(peopleRendered || agentsRendered) && (
235
260
  <div class="space-y-6">
236
261
  <SectionIntro
237
262
  eyebrow="Contributors"
@@ -258,6 +283,8 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
258
283
  ]}
259
284
  />
260
285
  </div>
286
+ )}
287
+ {booksRendered && (
261
288
  <div class="space-y-6">
262
289
  <SectionIntro
263
290
  eyebrow="Books"
@@ -275,16 +302,18 @@ const books = (await getCollection('books')).sort((a, b) => a.data.order - b.dat
275
302
  }))}
276
303
  />
277
304
  </div>
305
+ )}
278
306
  </div>
279
307
  </section>
308
+ )}
280
309
 
281
310
  <CTASection
282
311
  title="Follow the Treeseed from question to objective to book"
283
312
  body="If you are new here, start with current questions and objectives, then use the books and contributor pages to understand how TreeSeed organizes work across the platform."
284
- primaryHref="/questions/"
285
- primaryLabel="Browse questions"
286
- secondaryHref="/books/"
287
- secondaryLabel="Explore books"
313
+ primaryHref={ctaPrimaryHref}
314
+ primaryLabel={ctaPrimaryLabel}
315
+ secondaryHref={ctaSecondaryHref}
316
+ secondaryLabel={ctaSecondaryLabel}
288
317
  />
289
318
  </div>
290
319
  </MainLayout>
@@ -1,19 +1,26 @@
1
1
  ---
2
2
  import { getCollection, render } from 'astro:content';
3
3
  import NoteLayout from '../../layouts/NoteLayout.astro';
4
+ import RouteNotFound from '../../components/site/RouteNotFound.astro';
4
5
 
5
- export async function getStaticPaths() {
6
- const notes = await getCollection('notes', ({ data }) => !data.draft);
7
- return notes.map((note) => ({
8
- params: { slug: note.id },
9
- props: { note },
10
- }));
11
- }
6
+ export const prerender = false;
12
7
 
13
- const { note } = Astro.props;
14
- const { Content } = await render(note);
8
+ const slug = String(Astro.params.slug ?? '');
9
+ const notes = await getCollection('notes', ({ data }) => !data.draft);
10
+ const note = notes.find((candidate) => candidate.id === slug) ?? null;
11
+ if (!note) {
12
+ Astro.response.status = 404;
13
+ }
14
+ const rendered = note ? await render(note) : null;
15
+ const Content = rendered?.Content ?? null;
15
16
  ---
16
17
 
17
- <NoteLayout note={note.data}>
18
- <Content />
19
- </NoteLayout>
18
+ {
19
+ !note || !Content ? (
20
+ <RouteNotFound title="Note not found" description="The requested note could not be found in this Treeseed." currentPath="/notes/" />
21
+ ) : (
22
+ <NoteLayout note={note.data}>
23
+ <Content />
24
+ </NoteLayout>
25
+ )
26
+ }