@anthusai/papyrus 0.0.0 → 1.0.0-next.1

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/amplify/auth/resource.js +61 -0
  3. package/amplify/auth/resource.ts +65 -0
  4. package/amplify/data/resource.js +2037 -0
  5. package/amplify/data/resource.ts +2148 -0
  6. package/amplify/data/schema.js +1850 -0
  7. package/amplify/data/schema.ts +1953 -0
  8. package/amplify/functions/assignment-action/handler.ts +1416 -0
  9. package/amplify/functions/assignment-action/resource.js +8 -0
  10. package/amplify/functions/assignment-action/resource.ts +9 -0
  11. package/amplify/functions/category-action/handler.ts +2259 -0
  12. package/amplify/functions/category-action/resource.js +8 -0
  13. package/amplify/functions/category-action/resource.ts +9 -0
  14. package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
  15. package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
  16. package/amplify/functions/console-chat-responder/Dockerfile +19 -0
  17. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
  18. package/amplify/functions/console-chat-responder/resource.js +153 -0
  19. package/amplify/functions/console-chat-responder/resource.ts +179 -0
  20. package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
  21. package/amplify/functions/email-submission-processor/handler.py +46 -0
  22. package/amplify/functions/email-submission-processor/requirements.txt +2 -0
  23. package/amplify/functions/email-submission-processor/resource.js +56 -0
  24. package/amplify/functions/email-submission-processor/resource.ts +69 -0
  25. package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
  26. package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
  27. package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
  28. package/amplify/functions/knowledge-query/handler.py +1 -0
  29. package/amplify/functions/knowledge-query/requirements.txt +3 -0
  30. package/amplify/functions/knowledge-query/resource.js +50 -0
  31. package/amplify/functions/knowledge-query/resource.ts +61 -0
  32. package/amplify/functions/manage-user-role/handler.ts +799 -0
  33. package/amplify/functions/manage-user-role/resource.js +8 -0
  34. package/amplify/functions/manage-user-role/resource.ts +9 -0
  35. package/amplify/functions/model-attachment-upload/handler.ts +387 -0
  36. package/amplify/functions/model-attachment-upload/resource.js +8 -0
  37. package/amplify/functions/model-attachment-upload/resource.ts +9 -0
  38. package/amplify/functions/newsroom-summary/handler.ts +321 -0
  39. package/amplify/functions/newsroom-summary/resource.js +8 -0
  40. package/amplify/functions/newsroom-summary/resource.ts +9 -0
  41. package/amplify/functions/procedure-action/handler.ts +621 -0
  42. package/amplify/functions/procedure-action/resource.js +8 -0
  43. package/amplify/functions/procedure-action/resource.ts +9 -0
  44. package/amplify/functions/reader-settings/handler.ts +322 -0
  45. package/amplify/functions/reader-settings/resource.js +8 -0
  46. package/amplify/functions/reader-settings/resource.ts +9 -0
  47. package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
  48. package/amplify/functions/ses-inbound-receive/resource.js +8 -0
  49. package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
  50. package/amplify/functions/shared/amplify-secrets.ts +65 -0
  51. package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
  52. package/amplify/functions/shared/email-mime-intake.ts +196 -0
  53. package/amplify/functions/shared/email-submission.ts +415 -0
  54. package/amplify/functions/shared/inbound-email-intake.ts +37 -0
  55. package/amplify/functions/shared/lambda-data-client.ts +21 -0
  56. package/amplify/functions/shared/model-payloads.ts +186 -0
  57. package/amplify/functions/shared/python-bundle.js +115 -0
  58. package/amplify/functions/shared/python-bundle.ts +141 -0
  59. package/amplify/functions/shared/slack-events.ts +111 -0
  60. package/amplify/functions/slack-delivery/handler.py +112 -0
  61. package/amplify/functions/slack-delivery/requirements.txt +2 -0
  62. package/amplify/functions/slack-delivery/resource.js +38 -0
  63. package/amplify/functions/slack-delivery/resource.ts +51 -0
  64. package/amplify/functions/slack-events/handler.ts +155 -0
  65. package/amplify/functions/slack-events/resource.js +11 -0
  66. package/amplify/functions/slack-events/resource.ts +12 -0
  67. package/amplify/inbound-email/stack.js +46 -0
  68. package/amplify/inbound-email/stack.ts +61 -0
  69. package/amplify/package.json +3 -0
  70. package/amplify/site-backend-config.js +1 -0
  71. package/amplify/site-backend-config.ts +30 -0
  72. package/amplify/site-backend.js +587 -0
  73. package/amplify/site-backend.ts +704 -0
  74. package/amplify/storage/resource.js +18 -0
  75. package/amplify/storage/resource.ts +19 -0
  76. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
  77. package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
  78. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
  79. package/app/[year]/[month]/[day]/page.tsx +22 -0
  80. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
  81. package/app/api/archive/editions/route.ts +18 -0
  82. package/app/api/media/[...path]/route.ts +35 -0
  83. package/app/api/revalidate/route.ts +94 -0
  84. package/app/archive/page.tsx +24 -0
  85. package/app/articles/[slug]/page.tsx +35 -0
  86. package/app/globals.css +10329 -0
  87. package/app/layout.tsx +146 -0
  88. package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
  89. package/app/newsroom/[section]/page.tsx +47 -0
  90. package/app/newsroom/forum/[threadId]/page.tsx +35 -0
  91. package/app/newsroom/insights/[threadId]/page.tsx +22 -0
  92. package/app/newsroom/insights/page.tsx +14 -0
  93. package/app/newsroom/layout.tsx +12 -0
  94. package/app/newsroom/page.tsx +31 -0
  95. package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
  96. package/app/page.tsx +272 -0
  97. package/app/settings/page.tsx +14 -0
  98. package/app/tailwind.css +65 -0
  99. package/bin/papyrus-app.mjs +10 -0
  100. package/bin/papyrus-infra.mjs +39 -0
  101. package/components/ai-elements/conversation.tsx +168 -0
  102. package/components/ai-elements/message.tsx +360 -0
  103. package/components/ai-elements/model-selector.tsx +30 -0
  104. package/components/ai-elements/prompt-input.tsx +1461 -0
  105. package/components/ai-elements/shimmer.tsx +76 -0
  106. package/components/ai-elements/suggestion.tsx +54 -0
  107. package/components/ai-elements/tool.tsx +173 -0
  108. package/components/amplify-client-provider.tsx +140 -0
  109. package/components/archive-grid.tsx +89 -0
  110. package/components/archive-shell.tsx +41 -0
  111. package/components/article-video.tsx +47 -0
  112. package/components/blog-page-background.tsx +20 -0
  113. package/components/dev-sandbox-editor-sign-in.tsx +60 -0
  114. package/components/news-desk-client-provider.tsx +211 -0
  115. package/components/news-desk-page.tsx +119 -0
  116. package/components/news-desk-taxonomy-client.ts +3684 -0
  117. package/components/newspaper.tsx +1929 -0
  118. package/components/newsroom-assignments-view.tsx +664 -0
  119. package/components/newsroom-ops-overview.tsx +226 -0
  120. package/components/newsroom-ops-shell.tsx +319 -0
  121. package/components/newsroom-references-view.tsx +598 -0
  122. package/components/newsroom-topics-view.tsx +425 -0
  123. package/components/papyrus-console-shell.tsx +1912 -0
  124. package/components/pictogram-figure.tsx +65 -0
  125. package/components/presentation-footer.tsx +146 -0
  126. package/components/reader-auth-control.tsx +162 -0
  127. package/components/reader-auth-state.ts +122 -0
  128. package/components/reader-settings.ts +332 -0
  129. package/components/reader-theme-control.tsx +85 -0
  130. package/components/reference-source-preview.tsx +163 -0
  131. package/components/settings-page.tsx +239 -0
  132. package/components/topic-steering-workspace.tsx +18063 -0
  133. package/components/ui/badge.tsx +30 -0
  134. package/components/ui/button-group.tsx +87 -0
  135. package/components/ui/button.tsx +58 -0
  136. package/components/ui/card.tsx +32 -0
  137. package/components/ui/command.tsx +194 -0
  138. package/components/ui/dialog.tsx +158 -0
  139. package/components/ui/dropdown-menu.tsx +266 -0
  140. package/components/ui/hover-card.tsx +51 -0
  141. package/components/ui/input-group.tsx +158 -0
  142. package/components/ui/input.tsx +20 -0
  143. package/components/ui/scroll-area.tsx +16 -0
  144. package/components/ui/select.tsx +199 -0
  145. package/components/ui/separator.tsx +23 -0
  146. package/components/ui/sheet.tsx +92 -0
  147. package/components/ui/spinner.tsx +10 -0
  148. package/components/ui/tabs.tsx +113 -0
  149. package/components/ui/textarea.tsx +18 -0
  150. package/components/ui/tooltip.tsx +66 -0
  151. package/components/use-resolved-papyrus-theme.ts +47 -0
  152. package/infra/amplify-app-shell.js +109 -0
  153. package/infra/amplify-app-shell.ts +155 -0
  154. package/infra/index.js +12 -0
  155. package/lib/amplify-outputs-guard.ts +24 -0
  156. package/lib/amplify-outputs-path.ts +16 -0
  157. package/lib/amplify-server-runtime.ts +63 -0
  158. package/lib/archive-data.ts +30 -0
  159. package/lib/archive-types.ts +15 -0
  160. package/lib/articles.ts +320 -0
  161. package/lib/cached-content-repository.ts +89 -0
  162. package/lib/category-dashboard.ts +37 -0
  163. package/lib/category-repository.ts +1907 -0
  164. package/lib/console-chat-client.ts +1316 -0
  165. package/lib/console-web-context.ts +118 -0
  166. package/lib/content-load-timing.ts +23 -0
  167. package/lib/content-repository.ts +40 -0
  168. package/lib/content-types.ts +104 -0
  169. package/lib/define-site.js +3 -0
  170. package/lib/define-site.ts +21 -0
  171. package/lib/demo-amplify-outputs.ts +15 -0
  172. package/lib/dev-sandbox-editor-auth.ts +18 -0
  173. package/lib/doctrine.ts +128 -0
  174. package/lib/edition-routes.ts +155 -0
  175. package/lib/edition-sections.ts +111 -0
  176. package/lib/empty-edition-layout-plan.ts +109 -0
  177. package/lib/excerpts.ts +8 -0
  178. package/lib/graphql-content-repository.ts +986 -0
  179. package/lib/image-url.ts +10 -0
  180. package/lib/layout-plan.ts +981 -0
  181. package/lib/layout-scenarios.ts +564 -0
  182. package/lib/markus-body.ts +168 -0
  183. package/lib/markus-ir.ts +235 -0
  184. package/lib/markus-projection.ts +243 -0
  185. package/lib/markus-to-article.ts +93 -0
  186. package/lib/news-desk-session.ts +160 -0
  187. package/lib/newspaper-layout.ts +3459 -0
  188. package/lib/newsroom-assignments.ts +173 -0
  189. package/lib/newsroom-base-path.ts +48 -0
  190. package/lib/newsroom-card-layout.ts +102 -0
  191. package/lib/newsroom-category-drilldown.ts +133 -0
  192. package/lib/newsroom-demo-dashboard.ts +267 -0
  193. package/lib/newsroom-demo-profile.ts +46 -0
  194. package/lib/newsroom-forum-routes.ts +153 -0
  195. package/lib/newsroom-index-filters.ts +202 -0
  196. package/lib/newsroom-knowledge-query-request.ts +51 -0
  197. package/lib/newsroom-list-selection.ts +42 -0
  198. package/lib/newsroom-nav.ts +42 -0
  199. package/lib/newsroom-references.ts +116 -0
  200. package/lib/newsroom-sections.ts +52 -0
  201. package/lib/newsroom-topics.ts +118 -0
  202. package/lib/papyrus-object-kinds.ts +24 -0
  203. package/lib/papyrus-web-locations.ts +480 -0
  204. package/lib/pilobol-sample.ts +167 -0
  205. package/lib/presentation-footer.ts +92 -0
  206. package/lib/pretext-layout.ts +179 -0
  207. package/lib/public-placeholder-config.ts +102 -0
  208. package/lib/publication-items.ts +181 -0
  209. package/lib/reader-route-config.ts +1 -0
  210. package/lib/reader-static-params.ts +56 -0
  211. package/lib/reader-storage-url.ts +63 -0
  212. package/lib/reference-display.ts +74 -0
  213. package/lib/reference-policy.ts +97 -0
  214. package/lib/reference-source-preview.ts +175 -0
  215. package/lib/renderer-config.ts +50 -0
  216. package/lib/renderer.ts +87 -0
  217. package/lib/reporting-story-budget.ts +638 -0
  218. package/lib/semantic-graph.ts +554 -0
  219. package/lib/site-brand-tenant.ts +41 -0
  220. package/lib/site-brand.ts +179 -0
  221. package/lib/site-renderer.ts +17 -0
  222. package/lib/site-stack.ts +195 -0
  223. package/lib/themed-image.ts +30 -0
  224. package/lib/utils.ts +6 -0
  225. package/middleware.ts +78 -0
  226. package/package.json +296 -1
  227. package/publications/anth_us/brand.ts +68 -0
  228. package/publications/anth_us/theme.css +66 -0
  229. package/publications/papyrus/theme.css +11 -0
  230. package/publications/pilobol_us/brand.ts +37 -0
  231. package/publications/pilobol_us/theme.css +140 -0
  232. package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
  233. package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
  234. package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
  235. package/publications/threat_intelligence/brand.ts +40 -0
  236. package/publications/threat_intelligence/pictograms/art.tsx +608 -0
  237. package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
  238. package/publications/threat_intelligence/pictograms/registry.ts +30 -0
  239. package/publications/threat_intelligence/pictograms/system.tsx +305 -0
  240. package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
  241. package/publications/threat_intelligence/theme.css +1440 -0
  242. package/renderers/markus/index.ts +25 -0
  243. package/renderers/markus/stubs.tsx +33 -0
  244. package/renderers/pretext/article-page.tsx +174 -0
  245. package/renderers/pretext/index.ts +30 -0
  246. package/renderers/pretext/layouts.ts +16 -0
  247. package/renderers/pretext/presentation-shell.tsx +477 -0
  248. package/routes.manifest.json +182 -0
  249. package/scripts/fixtures/pilobol-sample.ast.json +234 -0
  250. package/src/backend.js +1 -0
  251. package/src/backend.ts +3 -0
  252. package/src/sync.mjs +87 -0
  253. package/src/with-papyrus.d.mts +2 -0
  254. package/src/with-papyrus.mjs +47 -0
@@ -0,0 +1,267 @@
1
+ import type { CategorySteeringDashboard } from "./category-repository";
2
+ import { getNewsroomDemoProfile, type NewsroomDemoProfile } from "./newsroom-demo-profile";
3
+ import type { SiteBrandId } from "./site-brand";
4
+
5
+ const PILOBOLUS_CATEGORY_LABELS: Record<string, { displayName: string; subtitle: string; description: string }> = {
6
+ "category.foundation-model-scaling": {
7
+ displayName: "Strangler-Fig Control Loops",
8
+ subtitle: "Host encirclement, canopy pressure, and takeover timing",
9
+ description: "Field notes on how strangler-fig encirclement reshapes access paths and control timing in Pilobolus research.",
10
+ },
11
+ "category.agent-memory": {
12
+ displayName: "Mycelial Signal Routing",
13
+ subtitle: "Network timing, propagation failure, and recovery paths",
14
+ description: "Accepted subcategory for signal routing, persistence, and coordination across fungal network observations.",
15
+ },
16
+ "category.benchmark-saturation": {
17
+ displayName: "Canopy Competition Metrics",
18
+ subtitle: "Light capture, host stress, and encirclement saturation signals",
19
+ description: "Metrics for when a host canopy loses effective control under sustained strangler pressure.",
20
+ },
21
+ "category.symbolic-connectionist-history": {
22
+ displayName: "Fungal Network Field Logs",
23
+ subtitle: "Seasonal intake logs and annotated field observations",
24
+ description: "Historical field logs and intake notes that anchor Pilobolus fungus-among-us coverage.",
25
+ },
26
+ };
27
+
28
+ export function applyPilobolusDemoDashboardContent(
29
+ dashboard: CategorySteeringDashboard,
30
+ profile: NewsroomDemoProfile = getNewsroomDemoProfile("pilobol-us"),
31
+ ): CategorySteeringDashboard {
32
+ const localized = structuredClone(dashboard);
33
+ localized.canonicalCorpusId = profile.corpusId;
34
+ localized.canonicalCategorySetId = profile.categorySetId;
35
+
36
+ for (const corpus of localized.corpora) {
37
+ if (corpus.role === "canonical") {
38
+ corpus.id = profile.corpusId;
39
+ corpus.name = profile.canonicalCorpusName;
40
+ }
41
+ if (corpus.role === "source") {
42
+ corpus.id = profile.sourceCorpusId;
43
+ corpus.name = profile.sourceCorpusName;
44
+ }
45
+ }
46
+
47
+ for (const run of localized.importRuns) {
48
+ run.classifierId = profile.classifierId;
49
+ if (run.corpusId.includes("demo-canonical")) run.corpusId = profile.corpusId;
50
+ if (run.corpusId.includes("demo-source")) run.corpusId = profile.sourceCorpusId;
51
+ }
52
+
53
+ for (const set of localized.categorySets) {
54
+ set.classifierId = profile.classifierId;
55
+ if (set.id === "category-set-demo-canonical") {
56
+ set.id = profile.categorySetId;
57
+ set.lineageId = profile.categorySetId;
58
+ set.corpusId = profile.corpusId;
59
+ set.displayName = profile.categorySetLabel;
60
+ set.description = "Accepted Pilobolus steering tree for fungus-among-us field coverage.";
61
+ }
62
+ if (set.id === "category-set-demo-source") {
63
+ set.id = profile.sourceCategorySetId;
64
+ set.lineageId = profile.sourceCategorySetId;
65
+ set.corpusId = profile.sourceCorpusId;
66
+ set.displayName = "Pilobolus Source Intake Tree";
67
+ set.description = "Source intake categories for Pilobolus field material.";
68
+ }
69
+ }
70
+
71
+ for (const tree of localized.categoryTrees) {
72
+ tree.classifierId = profile.classifierId;
73
+ tree.corpusId = profile.corpusId;
74
+ tree.displayName = `${profile.brandLabel} Steering Tree`;
75
+ tree.description = "Accepted topic tree for Pilobolus field-notes coverage.";
76
+ }
77
+
78
+ const relabelCategory = <T extends { categoryKey?: string | null; displayName?: string | null; subtitle?: string | null; description?: string | null }>(
79
+ row: T,
80
+ ): T => {
81
+ const labels = row.categoryKey ? PILOBOLUS_CATEGORY_LABELS[row.categoryKey] : undefined;
82
+ if (!labels) return row;
83
+ return {
84
+ ...row,
85
+ displayName: labels.displayName,
86
+ subtitle: labels.subtitle,
87
+ description: labels.description,
88
+ };
89
+ };
90
+
91
+ localized.categorys = localized.categorys.map(relabelCategory);
92
+ localized.categoryNodes = localized.categoryNodes.map(relabelCategory);
93
+
94
+ localized.categoryKeywords = localized.categoryKeywords.map((keyword) => {
95
+ if (keyword.categoryKey === "category.foundation-model-scaling") {
96
+ return { ...keyword, keyword: "strangler fig", normalizedKeyword: "strangler fig" };
97
+ }
98
+ if (keyword.categoryKey === "category.agent-memory") {
99
+ return { ...keyword, keyword: "mycelial routing", normalizedKeyword: "mycelial routing" };
100
+ }
101
+ return keyword;
102
+ });
103
+
104
+ localized.proposals = localized.proposals.map((proposal) => {
105
+ const relabeled = relabelCategory(proposal);
106
+ if (proposal.proposalKind === "rename-category") {
107
+ return {
108
+ ...relabeled,
109
+ title: "Rename strangler-fig control category",
110
+ summary: "Field intake consistently uses strangler-fig encirclement language instead of generic scaling terminology.",
111
+ };
112
+ }
113
+ if (proposal.proposalKind === "create-category") {
114
+ return {
115
+ ...relabeled,
116
+ title: "Create mycelial routing subcategory",
117
+ summary: "Discovery found a child topic under strangler-fig control loops for signal routing evidence.",
118
+ displayName: "Mycelial Signal Routing",
119
+ description: "Candidate subcategory covering routing, persistence, and propagation across fungal networks.",
120
+ };
121
+ }
122
+ if (proposal.proposalKind === "relationship-proposal") {
123
+ return {
124
+ ...relabeled,
125
+ title: "Link control loop category to canopy metrics entity",
126
+ summary: "Evidence suggests strangler-fig control loops relate to canopy competition metrics.",
127
+ };
128
+ }
129
+ return relabeled;
130
+ });
131
+
132
+ localized.references = localized.references.map((reference, index) => ({
133
+ ...reference,
134
+ corpusId: profile.sourceCorpusId,
135
+ title: index === 0
136
+ ? "Strangler Fig Host Encirclement Field Notes"
137
+ : "Mycelial Coordination Under Canopy Stress",
138
+ sourceUri: index === 0
139
+ ? "s3://pilobol-us-demo/corpora/pilobol-us-source/strangler-fig-field-notes-001.md"
140
+ : "s3://pilobol-us-demo/corpora/pilobol-us-source/mycelial-canopy-stress-002.md",
141
+ storagePath: index === 0
142
+ ? "corpora/pilobol-us-source/strangler-fig-field-notes-001.md"
143
+ : "corpora/pilobol-us-source/mycelial-canopy-stress-002.md",
144
+ }));
145
+
146
+ localized.referenceAttachments = localized.referenceAttachments.map((attachment, index) => ({
147
+ ...attachment,
148
+ sourceUri: index % 2 === 0
149
+ ? "s3://pilobol-us-demo/corpora/pilobol-us-source/strangler-fig-field-notes-001.md"
150
+ : "s3://pilobol-us-demo/corpora/pilobol-us-source/mycelial-canopy-stress-002.md",
151
+ storagePath: index % 2 === 0
152
+ ? "corpora/pilobol-us-source/strangler-fig-field-notes-001.md"
153
+ : "corpora/pilobol-us-source/mycelial-canopy-stress-002.md",
154
+ filename: index % 2 === 0 ? "strangler-fig-field-notes-001.md" : "mycelial-canopy-stress-002.md",
155
+ }));
156
+
157
+ localized.semanticNodes = localized.semanticNodes.map((node, index) => ({
158
+ ...node,
159
+ corpusId: index === 0 ? profile.corpusId : profile.sourceCorpusId,
160
+ displayName: index === 0 ? "Canopy Competition Metrics" : "Intake Rationale",
161
+ }));
162
+
163
+ localized.messages = localized.messages.map((message) => {
164
+ if (message.messageKind === "ingestion_rationale") {
165
+ return {
166
+ ...message,
167
+ body: "Imported as a useful holdout against generic platform demo intake.",
168
+ summary: "Pilobolus intake rationale",
169
+ };
170
+ }
171
+ if (message.messageKind === "reporting_context_packet") {
172
+ const metadata = message.metadata && typeof message.metadata === "object" && !Array.isArray(message.metadata)
173
+ ? message.metadata as Record<string, unknown>
174
+ : {};
175
+ const reporting = metadata.reporting && typeof metadata.reporting === "object" && !Array.isArray(metadata.reporting)
176
+ ? metadata.reporting as Record<string, unknown>
177
+ : {};
178
+ return {
179
+ ...message,
180
+ body: "Context packet for a Pilobolus field-notes candidate. Editors can select, merge, brief, hold, or kill this packet without creating reader copy.",
181
+ summary: "Reporting context packet: strangler-fig control-loop angle",
182
+ metadata: {
183
+ ...metadata,
184
+ reporting: {
185
+ ...reporting,
186
+ sectionKey: "field-notes",
187
+ recommendedAngle: "Focus on host encirclement timing and mycelial routing evidence.",
188
+ copywriterBrief: "Use accepted Pilobolus field-note evidence before drafting.",
189
+ },
190
+ },
191
+ };
192
+ }
193
+ return message;
194
+ });
195
+
196
+ localized.assignments = localized.assignments.map((assignment, index) => {
197
+ if (assignment.assignmentTypeKey === "reporting.edition-candidate") {
198
+ return {
199
+ ...assignment,
200
+ title: "Field-notes reporting candidate 1: Strangler-fig control-loop shift",
201
+ summary: "Create reporting context for a Pilobolus field-notes candidate; do not create a draft Item until editor selection.",
202
+ brief: "Report a section-ready candidate for Field Notes, option 1.",
203
+ primaryFocusCategoryKey: "category.foundation-model-scaling",
204
+ topicScopeCategoryKeys: ["category.foundation-model-scaling"],
205
+ };
206
+ }
207
+ if (index === 0) {
208
+ return {
209
+ ...assignment,
210
+ title: "Reference intake: strangler-fig field notes",
211
+ summary: "Review and curate Pilobolus source intake for strangler-fig encirclement field notes.",
212
+ };
213
+ }
214
+ return {
215
+ ...assignment,
216
+ title: "Reference intake: mycelial canopy stress log",
217
+ summary: "Review Pilobolus source intake for mycelial coordination under canopy stress.",
218
+ };
219
+ });
220
+
221
+ localized.doctrineRecords = localized.doctrineRecords.map((record) => {
222
+ const editorial = record.editorial && typeof record.editorial === "object" && !Array.isArray(record.editorial)
223
+ ? record.editorial as Record<string, unknown>
224
+ : null;
225
+ if (editorial?.kind === "mission") {
226
+ return {
227
+ ...record,
228
+ title: "Pilobolus Editorial Mission",
229
+ body: [
230
+ "Pilobolus publishes field notes that help readers see fungus-among-us coordination, strangler-fig control loops, and mycelial signal routing with inspectable evidence.",
231
+ "Coverage should stay source-disciplined, canopy-aware, and explicit about what remains uncertain in the field record.",
232
+ ],
233
+ };
234
+ }
235
+ if (editorial?.kind === "policy") {
236
+ return {
237
+ ...record,
238
+ title: "Pilobolus Editorial Policy",
239
+ body: [
240
+ "Publication doctrine is the global inclusion standard for Pilobol.us; desk doctrine sets local intake priorities for field notes and source intake.",
241
+ "Field-notes reporting should prioritize accepted Pilobolus references and annotated intake artifacts—not the Papyrus platform knowledge base.",
242
+ "High-risk claims about host takeover or network control require explicit uncertainty handling and accepted source linkage.",
243
+ ],
244
+ };
245
+ }
246
+ return record;
247
+ });
248
+
249
+ localized.artifacts = localized.artifacts.map((artifact) => ({
250
+ ...artifact,
251
+ corpusId: profile.corpusId,
252
+ artifactId: "s3://pilobol-us-demo/accepted-category-set.json",
253
+ displayName: "Pilobolus Accepted Steering Tree JSON",
254
+ }));
255
+
256
+ localized.lexicalSteeringRules = localized.lexicalSteeringRules.map((rule) => ({
257
+ ...rule,
258
+ source: "pilobol-us-lexical-steering.yml",
259
+ createdBy: "pilobol-us-config",
260
+ }));
261
+
262
+ return localized;
263
+ }
264
+
265
+ export function shouldUsePilobolusDemoDashboard(brandId: SiteBrandId): boolean {
266
+ return brandId === "pilobol-us";
267
+ }
@@ -0,0 +1,46 @@
1
+ import { resolveSiteBrandId, type SiteBrandId } from "./site-brand";
2
+
3
+ export type NewsroomDemoProfile = {
4
+ brandLabel: string;
5
+ canonicalCorpusName: string;
6
+ sourceCorpusName: string;
7
+ categorySetLabel: string;
8
+ classifierId: string;
9
+ corpusId: string;
10
+ sourceCorpusId: string;
11
+ categorySetId: string;
12
+ sourceCategorySetId: string;
13
+ };
14
+
15
+ const PAPYRUS_DEMO_PROFILE: NewsroomDemoProfile = {
16
+ brandLabel: "Papyrus",
17
+ canonicalCorpusName: "Canonical Demo Corpus",
18
+ sourceCorpusName: "Source Demo Corpus",
19
+ categorySetLabel: "Demo Canonical Category Set",
20
+ classifierId: "demo-canonical-classifier",
21
+ corpusId: "knowledge-corpus-demo-canonical",
22
+ sourceCorpusId: "knowledge-corpus-demo-source",
23
+ categorySetId: "category-set-demo-canonical",
24
+ sourceCategorySetId: "category-set-demo-source",
25
+ };
26
+
27
+ const PILOBOLUS_DEMO_PROFILE: NewsroomDemoProfile = {
28
+ brandLabel: "Pilobolus",
29
+ canonicalCorpusName: "Pilobolus Field Notes Corpus",
30
+ sourceCorpusName: "Pilobolus Source Intake",
31
+ categorySetLabel: "Pilobolus Steering Tree",
32
+ classifierId: "pilobolus-demo-classifier",
33
+ corpusId: "knowledge-corpus-pilobolus-demo-canonical",
34
+ sourceCorpusId: "knowledge-corpus-pilobolus-demo-source",
35
+ categorySetId: "category-set-pilobolus-demo-canonical",
36
+ sourceCategorySetId: "category-set-pilobolus-demo-source",
37
+ };
38
+
39
+ export function getNewsroomDemoProfile(brandId: SiteBrandId = resolveSiteBrandId()): NewsroomDemoProfile {
40
+ if (brandId === "pilobol-us") return PILOBOLUS_DEMO_PROFILE;
41
+ return PAPYRUS_DEMO_PROFILE;
42
+ }
43
+
44
+ export function isPilobolusDemoBrand(brandId: SiteBrandId = resolveSiteBrandId()): boolean {
45
+ return brandId === "pilobol-us";
46
+ }
@@ -0,0 +1,153 @@
1
+ export const FORUM_THREAD_ROUTE_PREFIX = "/newsroom/forum/";
2
+ export const INSIGHT_THREAD_ROUTE_PREFIX = "/newsroom/insights/";
3
+ export const FORUM_MESSAGE_HASH_PREFIX = "message-";
4
+
5
+ export function isForumThreadId(id: string | null | undefined): boolean {
6
+ const value = String(id ?? "").trim();
7
+ return value.startsWith("message-thread-");
8
+ }
9
+
10
+ export function isInsightThreadId(id: string | null | undefined): boolean {
11
+ const value = String(id ?? "").trim();
12
+ return Boolean(value) && !isForumThreadId(value);
13
+ }
14
+
15
+ export function getForumMessageAnchorId(messageId: string): string {
16
+ return `${FORUM_MESSAGE_HASH_PREFIX}${encodeURIComponent(messageId.trim())}`;
17
+ }
18
+
19
+ export function parseForumMessageAnchorFromHash(hash: string | null | undefined): string | null {
20
+ const raw = String(hash ?? "").replace(/^#/, "").trim();
21
+ if (!raw.startsWith(FORUM_MESSAGE_HASH_PREFIX)) return null;
22
+ const encoded = raw.slice(FORUM_MESSAGE_HASH_PREFIX.length);
23
+ if (!encoded) return null;
24
+ try {
25
+ return decodeURIComponent(encoded);
26
+ } catch {
27
+ return encoded;
28
+ }
29
+ }
30
+
31
+ export type ParsedNewsroomForumRoute = {
32
+ threadId: string | null;
33
+ messageId: string | null;
34
+ surface: "edition_forum" | "insight_forum" | null;
35
+ };
36
+
37
+ export function parseNewsroomForumRoute(pathname: string, hash?: string | null): ParsedNewsroomForumRoute {
38
+ const segments = pathname.split("/").filter(Boolean);
39
+ const newsroomIndex = segments.indexOf("newsroom");
40
+ if (newsroomIndex < 0) {
41
+ return { threadId: null, messageId: parseForumMessageAnchorFromHash(hash), surface: null };
42
+ }
43
+ const tail = segments.slice(newsroomIndex + 1);
44
+ if (tail[0] === "insights" && tail[1]) {
45
+ return {
46
+ threadId: decodeURIComponent(tail[1]),
47
+ messageId: parseForumMessageAnchorFromHash(hash),
48
+ surface: "insight_forum",
49
+ };
50
+ }
51
+ if (tail[0] === "forum" && tail[1]) {
52
+ return {
53
+ threadId: decodeURIComponent(tail[1]),
54
+ messageId: parseForumMessageAnchorFromHash(hash),
55
+ surface: "edition_forum",
56
+ };
57
+ }
58
+ if (tail[0] === "messages" && tail[1] === "forum" && tail[2]) {
59
+ return {
60
+ threadId: decodeURIComponent(tail[2]),
61
+ messageId: parseForumMessageAnchorFromHash(hash),
62
+ surface: "edition_forum",
63
+ };
64
+ }
65
+ if (tail[0] === "messages" && tail[1] && isForumThreadId(tail[1])) {
66
+ return {
67
+ threadId: decodeURIComponent(tail[1]),
68
+ messageId: parseForumMessageAnchorFromHash(hash),
69
+ surface: "edition_forum",
70
+ };
71
+ }
72
+ return { threadId: null, messageId: parseForumMessageAnchorFromHash(hash), surface: null };
73
+ }
74
+
75
+ export function buildForumThreadUrl(
76
+ threadId: string,
77
+ options?: { messageId?: string | null; demo?: boolean },
78
+ ): string {
79
+ const encodedThreadId = encodeURIComponent(threadId.trim());
80
+ const params = new URLSearchParams();
81
+ if (options?.demo) params.set("demo", "1");
82
+ const query = params.toString();
83
+ const hash = options?.messageId ? getForumMessageAnchorId(options.messageId) : "";
84
+ return `/newsroom/forum/${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
85
+ }
86
+
87
+ export function buildInsightThreadUrl(
88
+ threadId: string,
89
+ options?: { messageId?: string | null; demo?: boolean },
90
+ ): string {
91
+ const encodedThreadId = encodeURIComponent(threadId.trim());
92
+ const params = new URLSearchParams();
93
+ if (options?.demo) params.set("demo", "1");
94
+ const query = params.toString();
95
+ const hash = options?.messageId ? getForumMessageAnchorId(options.messageId) : "";
96
+ return `${INSIGHT_THREAD_ROUTE_PREFIX}${encodedThreadId}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
97
+ }
98
+
99
+ export function buildThreadUrlForSurface(
100
+ surface: ParsedNewsroomForumRoute["surface"],
101
+ threadId: string,
102
+ options?: { messageId?: string | null; demo?: boolean },
103
+ ): string {
104
+ if (surface === "insight_forum") return buildInsightThreadUrl(threadId, options);
105
+ return buildForumThreadUrl(threadId, options);
106
+ }
107
+
108
+ export function readCurrentForumRoute(): ParsedNewsroomForumRoute {
109
+ if (typeof window === "undefined") {
110
+ return { threadId: null, messageId: null, surface: null };
111
+ }
112
+ return parseNewsroomForumRoute(window.location.pathname, window.location.hash);
113
+ }
114
+
115
+ export function pushForumThreadUrl(
116
+ threadId: string | null,
117
+ options?: { messageId?: string | null; demo?: boolean; replace?: boolean; surface?: ParsedNewsroomForumRoute["surface"] },
118
+ ) {
119
+ if (typeof window === "undefined") return;
120
+ const surface = options?.surface ?? readCurrentForumRoute().surface;
121
+ const url = threadId
122
+ ? buildThreadUrlForSurface(surface, threadId, options)
123
+ : (surface === "insight_forum" ? buildNewsroomInsightsIndexUrl(options?.demo) : buildNewsroomForumIndexUrl(options?.demo));
124
+ const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;
125
+ if (current === url) return;
126
+ if (options?.replace) {
127
+ window.history.replaceState(null, "", url);
128
+ } else {
129
+ window.history.pushState(null, "", url);
130
+ }
131
+ }
132
+
133
+ export function pushInsightThreadUrl(
134
+ threadId: string | null,
135
+ options?: { messageId?: string | null; demo?: boolean; replace?: boolean },
136
+ ) {
137
+ pushForumThreadUrl(threadId, { ...options, surface: "insight_forum" });
138
+ }
139
+
140
+ export function buildNewsroomForumIndexUrl(demo?: boolean): string {
141
+ const params = new URLSearchParams();
142
+ if (demo) params.set("demo", "1");
143
+ const query = params.toString();
144
+ return query ? `/newsroom?${query}` : "/newsroom";
145
+ }
146
+
147
+ export function buildNewsroomInsightsIndexUrl(demo?: boolean, domain?: string): string {
148
+ const params = new URLSearchParams();
149
+ if (demo) params.set("demo", "1");
150
+ if (domain?.trim()) params.set("domain", domain.trim());
151
+ const query = params.toString();
152
+ return query ? `/newsroom/insights?${query}` : "/newsroom/insights";
153
+ }
@@ -0,0 +1,202 @@
1
+ export type NewsroomIndexTab = "references" | "messages" | "assignments" | "insights";
2
+
3
+ export type ReferenceIndexOrder = "published" | "imported";
4
+
5
+ export type ReferencesIndexFilters = { status: string; processing: string; order: ReferenceIndexOrder };
6
+ export type MessagesIndexFilters = { kind: string; domain: string };
7
+ export type AssignmentsIndexFilters = { status: string; type: string; view: string };
8
+ export type InsightsIndexFilters = { domain: string };
9
+
10
+ export const DEFAULT_REFERENCES_INDEX_FILTERS: ReferencesIndexFilters = {
11
+ status: "exclude-pending",
12
+ processing: "",
13
+ order: "published",
14
+ };
15
+
16
+ export const DEFAULT_MESSAGES_INDEX_FILTERS: MessagesIndexFilters = {
17
+ kind: "",
18
+ domain: "",
19
+ };
20
+
21
+ export const DEFAULT_ASSIGNMENTS_INDEX_FILTERS: AssignmentsIndexFilters = {
22
+ status: "",
23
+ type: "",
24
+ view: "queue",
25
+ };
26
+
27
+ export const DEFAULT_INSIGHTS_INDEX_FILTERS: InsightsIndexFilters = {
28
+ domain: "",
29
+ };
30
+
31
+ export function referencesStatusFromUrl(value: string): string {
32
+ return value === "exclude-pending" ? "__exclude_pending" : value;
33
+ }
34
+
35
+ export function referencesStatusToUrl(value: string): string {
36
+ return value === "__exclude_pending" ? "exclude-pending" : value;
37
+ }
38
+
39
+ export function normalizeReferenceIndexOrder(value: string | null | undefined): ReferenceIndexOrder {
40
+ return value?.trim() === "imported" ? "imported" : "published";
41
+ }
42
+
43
+ export function effectiveReferencesIndexFilters(partial?: Partial<ReferencesIndexFilters>): ReferencesIndexFilters {
44
+ const order = normalizeReferenceIndexOrder(partial?.order);
45
+ const explicitStatus = partial?.status?.trim();
46
+ let status = explicitStatus || DEFAULT_REFERENCES_INDEX_FILTERS.status;
47
+ // Import-date sort is for intake triage; the default "reviewed only" filter hides
48
+ // pending references created by inbound email and other intake paths.
49
+ if (order === "imported" && !explicitStatus) {
50
+ status = "";
51
+ }
52
+ return {
53
+ status,
54
+ processing: partial?.processing?.trim() ?? DEFAULT_REFERENCES_INDEX_FILTERS.processing,
55
+ order,
56
+ };
57
+ }
58
+
59
+ export function effectiveMessagesIndexFilters(partial?: Partial<MessagesIndexFilters>): MessagesIndexFilters {
60
+ return {
61
+ kind: partial?.kind?.trim() ?? DEFAULT_MESSAGES_INDEX_FILTERS.kind,
62
+ domain: partial?.domain?.trim() ?? DEFAULT_MESSAGES_INDEX_FILTERS.domain,
63
+ };
64
+ }
65
+
66
+ export function effectiveAssignmentsIndexFilters(partial?: Partial<AssignmentsIndexFilters>): AssignmentsIndexFilters {
67
+ return {
68
+ status: partial?.status?.trim() ?? DEFAULT_ASSIGNMENTS_INDEX_FILTERS.status,
69
+ type: partial?.type?.trim() ?? DEFAULT_ASSIGNMENTS_INDEX_FILTERS.type,
70
+ view: partial?.view?.trim() || DEFAULT_ASSIGNMENTS_INDEX_FILTERS.view,
71
+ };
72
+ }
73
+
74
+ export function effectiveInsightsIndexFilters(partial?: Partial<InsightsIndexFilters>): InsightsIndexFilters {
75
+ return {
76
+ domain: partial?.domain?.trim() ?? DEFAULT_INSIGHTS_INDEX_FILTERS.domain,
77
+ };
78
+ }
79
+
80
+ export function readReferencesIndexFilters(searchParams: URLSearchParams): ReferencesIndexFilters {
81
+ const statusParam = searchParams.get("status")?.trim() ?? "";
82
+ const orderParam = searchParams.get("order")?.trim();
83
+ return effectiveReferencesIndexFilters({
84
+ status: statusParam ? referencesStatusFromUrl(statusParam) : undefined,
85
+ processing: searchParams.get("processing")?.trim() ?? undefined,
86
+ order: orderParam ? normalizeReferenceIndexOrder(orderParam) : undefined,
87
+ });
88
+ }
89
+
90
+ export function readMessagesIndexFilters(searchParams: URLSearchParams): MessagesIndexFilters {
91
+ return effectiveMessagesIndexFilters({
92
+ kind: searchParams.get("kind")?.trim() ?? undefined,
93
+ domain: searchParams.get("domain")?.trim() ?? undefined,
94
+ });
95
+ }
96
+
97
+ export function readAssignmentsIndexFilters(searchParams: URLSearchParams): AssignmentsIndexFilters {
98
+ return effectiveAssignmentsIndexFilters({
99
+ status: searchParams.get("status")?.trim() ?? undefined,
100
+ type: searchParams.get("type")?.trim() ?? undefined,
101
+ view: searchParams.get("view")?.trim() ?? undefined,
102
+ });
103
+ }
104
+
105
+ export function readInsightsIndexFilters(searchParams: URLSearchParams): InsightsIndexFilters {
106
+ return effectiveInsightsIndexFilters({
107
+ domain: searchParams.get("domain")?.trim() ?? undefined,
108
+ });
109
+ }
110
+
111
+ export function buildReferencesIndexQuery(filters: ReferencesIndexFilters): string {
112
+ const params = new URLSearchParams();
113
+ if (filters.status && filters.status !== DEFAULT_REFERENCES_INDEX_FILTERS.status) {
114
+ params.set("status", referencesStatusToUrl(filters.status));
115
+ }
116
+ if (filters.processing.trim()) params.set("processing", filters.processing.trim());
117
+ if (filters.order === "imported") params.set("order", "imported");
118
+ const query = params.toString();
119
+ return query ? `?${query}` : "";
120
+ }
121
+
122
+ export function buildMessagesIndexQuery(filters: MessagesIndexFilters): string {
123
+ const params = new URLSearchParams();
124
+ if (filters.kind.trim()) params.set("kind", filters.kind.trim());
125
+ if (filters.domain.trim()) params.set("domain", filters.domain.trim());
126
+ const query = params.toString();
127
+ return query ? `?${query}` : "";
128
+ }
129
+
130
+ export function buildAssignmentsIndexQuery(filters: AssignmentsIndexFilters): string {
131
+ const params = new URLSearchParams();
132
+ if (filters.status.trim()) params.set("status", filters.status.trim());
133
+ if (filters.type.trim()) params.set("type", filters.type.trim());
134
+ if (filters.view.trim() === "budget") params.set("view", "budget");
135
+ const query = params.toString();
136
+ return query ? `?${query}` : "";
137
+ }
138
+
139
+ export function buildInsightsIndexQuery(filters: InsightsIndexFilters): string {
140
+ const params = new URLSearchParams();
141
+ if (filters.domain.trim()) params.set("domain", filters.domain.trim());
142
+ const query = params.toString();
143
+ return query ? `?${query}` : "";
144
+ }
145
+
146
+ export function buildNewsroomIndexWebPath(
147
+ tab: NewsroomIndexTab,
148
+ filters: ReferencesIndexFilters | MessagesIndexFilters | AssignmentsIndexFilters | InsightsIndexFilters,
149
+ ): string {
150
+ if (tab === "references") return `/newsroom/references${buildReferencesIndexQuery(filters as ReferencesIndexFilters)}`;
151
+ if (tab === "messages") return `/newsroom/messages${buildMessagesIndexQuery(filters as MessagesIndexFilters)}`;
152
+ if (tab === "insights") return `/newsroom/insights${buildInsightsIndexQuery(filters as InsightsIndexFilters)}`;
153
+ return `/newsroom/assignments${buildAssignmentsIndexQuery(filters as AssignmentsIndexFilters)}`;
154
+ }
155
+
156
+ export function syncBrowserNewsroomIndexUrl(
157
+ tab: NewsroomIndexTab,
158
+ filters: ReferencesIndexFilters | MessagesIndexFilters | AssignmentsIndexFilters | InsightsIndexFilters,
159
+ options?: { replace?: boolean },
160
+ ) {
161
+ if (typeof window === "undefined") return;
162
+ const nextPath = buildNewsroomIndexWebPath(tab, filters);
163
+ const current = `${window.location.pathname}${window.location.search}`;
164
+ if (current === nextPath) return;
165
+ if (options?.replace) window.history.replaceState(null, "", nextPath);
166
+ else window.history.pushState(null, "", nextPath);
167
+ }
168
+
169
+ /** Lineage id from `/newsroom/references/<id>` (not index-only URLs). */
170
+ export function parseReferenceLineageIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
171
+ if (!pathname?.startsWith("/newsroom/references/")) return null;
172
+ const segment = pathname.slice("/newsroom/references/".length).split("/")[0]?.trim() ?? "";
173
+ if (!segment) return null;
174
+ try {
175
+ return decodeURIComponent(segment);
176
+ } catch {
177
+ return segment;
178
+ }
179
+ }
180
+
181
+ /** Assignment id from `/newsroom/assignments/<id>` (not index-only URLs). */
182
+ export function parseAssignmentIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
183
+ if (!pathname?.startsWith("/newsroom/assignments/")) return null;
184
+ const segment = pathname.slice("/newsroom/assignments/".length).split("/")[0]?.trim() ?? "";
185
+ if (!segment) return null;
186
+ try {
187
+ return decodeURIComponent(segment);
188
+ } catch {
189
+ return segment;
190
+ }
191
+ }
192
+
193
+ export function parseInsightThreadIdFromNewsroomPathname(pathname: string | null | undefined): string | null {
194
+ if (!pathname?.startsWith("/newsroom/insights/")) return null;
195
+ const segment = pathname.slice("/newsroom/insights/".length).split("/")[0]?.trim() ?? "";
196
+ if (!segment) return null;
197
+ try {
198
+ return decodeURIComponent(segment);
199
+ } catch {
200
+ return segment;
201
+ }
202
+ }