@anthusai/papyrus 1.4.0-next.3 → 1.4.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/amplify/data/resource.js +3 -3
- package/amplify/data/resource.ts +3 -3
- package/amplify/functions/category-action/handler.ts +18 -18
- package/amplify/functions/console-chat-responder/src/main.rs +1 -1
- package/amplify/functions/content-actions/handler.py +23 -0
- package/amplify/functions/shared/slack-events.ts +2 -2
- package/app/api/local-preview/derive/route.ts +28 -0
- package/app/api/local-preview/media/route.ts +25 -0
- package/app/globals.css +28 -28
- package/app/layout.tsx +8 -5
- package/app/newsroom/[section]/[...selection]/page.tsx +0 -1
- package/app/newsroom/[section]/page.tsx +0 -1
- package/app/newsroom/insights/[threadId]/page.tsx +5 -16
- package/app/newsroom/insights/page.tsx +4 -12
- package/components/board-card-excerpts.tsx +45 -0
- package/components/markus-ir-preview.tsx +47 -2
- package/components/news-desk-page.tsx +0 -1
- package/components/news-desk-taxonomy-client.ts +28 -222
- package/components/newsroom-article-editor.tsx +138 -40
- package/components/newsroom-assignment-detail.tsx +79 -0
- package/components/newsroom-board-view.tsx +44 -9
- package/components/newsroom-newsprint-board.css +10 -2
- package/components/newsroom-ops-overview.tsx +27 -5
- package/components/newsroom-organizer-shell.tsx +21 -6
- package/components/newsroom-organizer.css +208 -6
- package/components/newsroom-topics-view.tsx +1 -1
- package/components/priming-proof-page.tsx +17 -8
- package/components/story-reporting-trail-client.ts +91 -0
- package/components/story-reporting-trail.tsx +115 -0
- package/components/topic-steering-workspace.tsx +154 -671
- package/components/ui/sheet.tsx +10 -4
- package/components/wiki-concepts-view.tsx +153 -0
- package/components/wiki-index-client.ts +51 -0
- package/lib/approved-priming-proof-reports.ts +6 -3
- package/lib/approved-priming-proofs/papyrus.json +90 -0
- package/lib/content-actions-client.ts +3 -0
- package/lib/content-actions-demo.ts +55 -0
- package/lib/content-item-lifecycle.ts +4 -2
- package/lib/front-matter-fields.ts +42 -0
- package/lib/front-mosaic.ts +2 -1
- package/lib/layout-plan.ts +3 -2
- package/lib/layout-warnings.ts +8 -0
- package/lib/local-preview-seed.ts +53 -0
- package/lib/newsroom-articles.ts +2 -0
- package/lib/newsroom-assignments.ts +13 -0
- package/lib/newsroom-board.ts +4 -0
- package/lib/newsroom-forum-routes.ts +2 -43
- package/lib/newsroom-index-filters.ts +3 -40
- package/lib/newsroom-media-backend.ts +5 -0
- package/lib/newsroom-nav.ts +1 -3
- package/lib/newsroom-organizer-nav.ts +5 -12
- package/lib/newsroom-section-routing.ts +1 -1
- package/lib/newsroom-sections.ts +12 -0
- package/lib/papyrus-web-locations.ts +3 -24
- package/lib/priming-proof-report.ts +1 -0
- package/lib/semantic-graph.ts +5 -5
- package/lib/site-brand.ts +9 -0
- package/lib/story-reporting-trail.ts +106 -0
- package/lib/wiki.ts +56 -0
- package/package.json +13 -1
- package/routes.manifest.json +23 -7
package/amplify/data/resource.js
CHANGED
|
@@ -227,7 +227,7 @@ const schema = a.schema({
|
|
|
227
227
|
status: a.string().required(),
|
|
228
228
|
relationId: a.id(),
|
|
229
229
|
}),
|
|
230
|
-
|
|
230
|
+
ReferenceNoteActionResult: a.customType({
|
|
231
231
|
ok: a.boolean().required(),
|
|
232
232
|
referenceId: a.id().required(),
|
|
233
233
|
messageId: a.id().required(),
|
|
@@ -309,7 +309,7 @@ const schema = a.schema({
|
|
|
309
309
|
allow.groups(categoryWriteGroups),
|
|
310
310
|
])
|
|
311
311
|
.handler(a.handler.function(categoryAction)),
|
|
312
|
-
|
|
312
|
+
createReferenceNote: a
|
|
313
313
|
.mutation()
|
|
314
314
|
.arguments({
|
|
315
315
|
referenceId: a.id().required(),
|
|
@@ -318,7 +318,7 @@ const schema = a.schema({
|
|
|
318
318
|
actorSub: a.string(),
|
|
319
319
|
actorLabel: a.string(),
|
|
320
320
|
})
|
|
321
|
-
.returns(a.ref("
|
|
321
|
+
.returns(a.ref("ReferenceNoteActionResult"))
|
|
322
322
|
.authorization((allow) => [
|
|
323
323
|
allow.groups(categoryWriteGroups),
|
|
324
324
|
])
|
package/amplify/data/resource.ts
CHANGED
|
@@ -246,7 +246,7 @@ const schema = a.schema({
|
|
|
246
246
|
relationId: a.id(),
|
|
247
247
|
}),
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
ReferenceNoteActionResult: a.customType({
|
|
250
250
|
ok: a.boolean().required(),
|
|
251
251
|
referenceId: a.id().required(),
|
|
252
252
|
messageId: a.id().required(),
|
|
@@ -336,7 +336,7 @@ const schema = a.schema({
|
|
|
336
336
|
])
|
|
337
337
|
.handler(a.handler.function(categoryAction)),
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
createReferenceNote: a
|
|
340
340
|
.mutation()
|
|
341
341
|
.arguments({
|
|
342
342
|
referenceId: a.id().required(),
|
|
@@ -345,7 +345,7 @@ const schema = a.schema({
|
|
|
345
345
|
actorSub: a.string(),
|
|
346
346
|
actorLabel: a.string(),
|
|
347
347
|
})
|
|
348
|
-
.returns(a.ref("
|
|
348
|
+
.returns(a.ref("ReferenceNoteActionResult"))
|
|
349
349
|
.authorization((allow) => [
|
|
350
350
|
allow.groups(categoryWriteGroups),
|
|
351
351
|
])
|
|
@@ -9,7 +9,7 @@ import { referenceCurationMetadata } from "../shared/reference-curation-metadata
|
|
|
9
9
|
type ReviewHandler = Schema["reviewSteeringProposal"]["functionHandler"];
|
|
10
10
|
type ReferenceCurationHandler = Schema["reviewReferenceCuration"]["functionHandler"];
|
|
11
11
|
type ReferenceQualityHandler = Schema["setReferenceQualityRating"]["functionHandler"];
|
|
12
|
-
type
|
|
12
|
+
type ReferenceNoteHandler = Schema["createReferenceNote"]["functionHandler"];
|
|
13
13
|
type MoveReferenceCorpusHandler = Schema["moveReferenceCorpus"]["functionHandler"];
|
|
14
14
|
type StartReferenceCurationHandler = Schema["startReferenceCuration"]["functionHandler"];
|
|
15
15
|
type GetReferenceCurationStatusHandler = Schema["getReferenceCurationStatus"]["functionHandler"];
|
|
@@ -24,7 +24,7 @@ type CategoryActionEvent =
|
|
|
24
24
|
| Parameters<ReviewHandler>[0]
|
|
25
25
|
| Parameters<ReferenceCurationHandler>[0]
|
|
26
26
|
| Parameters<ReferenceQualityHandler>[0]
|
|
27
|
-
| Parameters<
|
|
27
|
+
| Parameters<ReferenceNoteHandler>[0]
|
|
28
28
|
| Parameters<MoveReferenceCorpusHandler>[0]
|
|
29
29
|
| Parameters<StartReferenceCurationHandler>[0]
|
|
30
30
|
| Parameters<GetReferenceCurationStatusHandler>[0]
|
|
@@ -89,7 +89,7 @@ export const handler = async (event: CategoryActionEvent) => {
|
|
|
89
89
|
if (fieldName === "reviewSteeringProposal") return reviewSteeringProposal(event as Parameters<ReviewHandler>[0]);
|
|
90
90
|
if (fieldName === "reviewReferenceCuration") return reviewReferenceCuration(event as Parameters<ReferenceCurationHandler>[0]);
|
|
91
91
|
if (fieldName === "setReferenceQualityRating") return setReferenceQualityRating(event as Parameters<ReferenceQualityHandler>[0]);
|
|
92
|
-
if (fieldName === "
|
|
92
|
+
if (fieldName === "createReferenceNote") return createReferenceNote(event as Parameters<ReferenceNoteHandler>[0]);
|
|
93
93
|
if (fieldName === "moveReferenceCorpus") return moveReferenceCorpus(event as Parameters<MoveReferenceCorpusHandler>[0]);
|
|
94
94
|
if (fieldName === "startReferenceCuration") return startReferenceCuration(event as Parameters<StartReferenceCurationHandler>[0]);
|
|
95
95
|
if (fieldName === "getReferenceCurationStatus") return getReferenceCurationStatus(event as Parameters<GetReferenceCurationStatusHandler>[0]);
|
|
@@ -403,7 +403,7 @@ async function setReferenceQualityRating(event: Parameters<ReferenceQualityHandl
|
|
|
403
403
|
};
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
async function
|
|
406
|
+
async function createReferenceNote(event: Parameters<ReferenceNoteHandler>[0]) {
|
|
407
407
|
const client = await getDataClient();
|
|
408
408
|
const referenceId = normalizeRequiredString(event.arguments.referenceId, "referenceId");
|
|
409
409
|
const summary = normalizeRequiredString(event.arguments.summary, "summary");
|
|
@@ -414,10 +414,10 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
414
414
|
const actorLabel = normalizeOptionalString(event.arguments.actorLabel) ?? getIdentityLabel(event);
|
|
415
415
|
const actor = actorLabel ?? actorSub ?? "Papyrus newsroom";
|
|
416
416
|
const referenceLineageId = normalizeOptionalString(reference.lineageId) ?? referenceId;
|
|
417
|
-
const messageId = `message-reference-
|
|
417
|
+
const messageId = `message-reference-note-${safeId(referenceLineageId)}-${now.replace(/[^0-9TZ]/g, "")}-${randomUUID().slice(0, 8)}`;
|
|
418
418
|
const relationId = `semantic-relation-${hashStable([
|
|
419
419
|
`message#${messageId}`,
|
|
420
|
-
"
|
|
420
|
+
"note_about",
|
|
421
421
|
`reference#${referenceId}`,
|
|
422
422
|
]).slice(0, 24)}`;
|
|
423
423
|
|
|
@@ -425,7 +425,7 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
425
425
|
client.models.Message.create({
|
|
426
426
|
...buildCanonicalMessageInput({
|
|
427
427
|
id: messageId,
|
|
428
|
-
messageKind: "
|
|
428
|
+
messageKind: "note",
|
|
429
429
|
messageDomain: "knowledge",
|
|
430
430
|
summary,
|
|
431
431
|
source: "newsroom",
|
|
@@ -436,7 +436,7 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
436
436
|
threadId: messageId,
|
|
437
437
|
sequenceNumber: 1,
|
|
438
438
|
}),
|
|
439
|
-
"create
|
|
439
|
+
"create note Message",
|
|
440
440
|
);
|
|
441
441
|
await putTextModelPayload(
|
|
442
442
|
client as any,
|
|
@@ -452,7 +452,7 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
452
452
|
"metadata",
|
|
453
453
|
"metadata",
|
|
454
454
|
{
|
|
455
|
-
kind: "reference.
|
|
455
|
+
kind: "reference.note.created",
|
|
456
456
|
targetKind: "reference",
|
|
457
457
|
targetId: referenceId,
|
|
458
458
|
targetLineageId: referenceLineageId,
|
|
@@ -463,8 +463,8 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
463
463
|
client.models.SemanticRelation.create({
|
|
464
464
|
id: relationId,
|
|
465
465
|
relationState: "current",
|
|
466
|
-
predicate: "
|
|
467
|
-
...semanticRelationTypeFieldsForPredicate("
|
|
466
|
+
predicate: "note_about",
|
|
467
|
+
...semanticRelationTypeFieldsForPredicate("note_about"),
|
|
468
468
|
subjectKind: "message",
|
|
469
469
|
subjectId: messageId,
|
|
470
470
|
subjectLineageId: messageId,
|
|
@@ -476,7 +476,7 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
476
476
|
subjectStateKey: semanticStateKey("message", messageId),
|
|
477
477
|
objectStateKey: semanticStateKey("reference", referenceLineageId),
|
|
478
478
|
objectSubjectStateKey: `${semanticStateKey("reference", referenceLineageId)}#message`,
|
|
479
|
-
predicateObjectStateKey: `
|
|
479
|
+
predicateObjectStateKey: `note_about#${semanticStateKey("reference", referenceLineageId)}`,
|
|
480
480
|
subjectVersionKey: semanticVersionKey("message", messageId),
|
|
481
481
|
objectVersionKey: semanticVersionKey("reference", referenceId),
|
|
482
482
|
score: 1,
|
|
@@ -492,17 +492,17 @@ async function createReferenceInsight(event: Parameters<ReferenceInsightHandler>
|
|
|
492
492
|
updatedAt: now,
|
|
493
493
|
newsroomFeedKey: "semanticRelations",
|
|
494
494
|
metadata: JSON.stringify({
|
|
495
|
-
kind: "reference.
|
|
495
|
+
kind: "reference.note",
|
|
496
496
|
summary,
|
|
497
497
|
actor,
|
|
498
498
|
}),
|
|
499
499
|
}),
|
|
500
|
-
"create
|
|
500
|
+
"create note_about relation",
|
|
501
501
|
);
|
|
502
502
|
await updateNewsroomSummaryForSemanticRelationDelta(
|
|
503
503
|
client,
|
|
504
504
|
{
|
|
505
|
-
relationTypeKey: "
|
|
505
|
+
relationTypeKey: "note_about",
|
|
506
506
|
relationDomain: "commentary",
|
|
507
507
|
subjectKind: "message",
|
|
508
508
|
objectKind: "reference",
|
|
@@ -1497,10 +1497,10 @@ function semanticRelationTypeFieldsForPredicate(predicate: string): {
|
|
|
1497
1497
|
relationDomain: "curation",
|
|
1498
1498
|
};
|
|
1499
1499
|
}
|
|
1500
|
-
if (predicate === "
|
|
1500
|
+
if (predicate === "note_about") {
|
|
1501
1501
|
return {
|
|
1502
|
-
relationTypeId: "semantic-relation-type-
|
|
1503
|
-
relationTypeKey: "
|
|
1502
|
+
relationTypeId: "semantic-relation-type-note-about",
|
|
1503
|
+
relationTypeKey: "note_about",
|
|
1504
1504
|
relationDomain: "knowledge",
|
|
1505
1505
|
};
|
|
1506
1506
|
}
|
|
@@ -2190,7 +2190,7 @@ fn build_openai_messages(
|
|
|
2190
2190
|
) -> Vec<Value> {
|
|
2191
2191
|
let mut messages = vec![json!({
|
|
2192
2192
|
"role": "system",
|
|
2193
|
-
"content": "You are Papyrus, an editorial assistant for an autonomous newsroom. Be concise, accurate, and concrete. Raw console chat turns are working memory and are excluded from default semantic searches unless explicitly requested. When a chat produces durable
|
|
2193
|
+
"content": "You are Papyrus, an editorial assistant for an autonomous newsroom. Be concise, accurate, and concrete. Raw console chat turns are working memory and are excluded from default semantic searches unless explicitly requested. When a chat produces durable note, recommend creating a note Message instead of making every chat turn canonical knowledge. Use execute_tactus for Papyrus runtime work. Distinguish references from assignments: Reference.* is for scholarly sources; Assignment.* is for newsroom work items (research, curation, intake). When the user provides a URL and asks to add, register, or file a reference, immediately call Reference.create{ url = \"<url>\", title = \"<optional>\", apply = true }—do not use Reference.register, papyrus.reference.create, or Assignment.create unless they asked for research work. For \"most recent references\" or similar, immediately call Reference.list{ limit = <count>, order = \"newest\" } and summarize. For \"last/latest/most recent research assignment\" (or \"research assignment\" without a specific id), immediately call Assignment.list{ type = \"research\", limit = 1 } (includes research.edition-candidate, research.tavily-deep, and other research.* types), then Assignment.get{ id = \"<that id>\" } if the user wants detail—do not use Reference.list and do not list all assignment types (curation.reference-intake is not a research assignment). To create research work, use Assignment.create{ type = \"research\", title = \"...\", apply = true }."
|
|
2194
2194
|
})];
|
|
2195
2195
|
if !static_prompt.publication_mission.trim().is_empty() {
|
|
2196
2196
|
messages.push(json!({
|
|
@@ -22,6 +22,7 @@ from papyrus_content.rebuild_trigger import (
|
|
|
22
22
|
trigger_rebuild,
|
|
23
23
|
trigger_staging_build,
|
|
24
24
|
)
|
|
25
|
+
from papyrus_content.story_reporting import save_story_version
|
|
25
26
|
from papyrus_content.publishing import (
|
|
26
27
|
ItemFields,
|
|
27
28
|
PublishError,
|
|
@@ -188,6 +189,28 @@ def run_projection_task(task: dict) -> dict:
|
|
|
188
189
|
|
|
189
190
|
def save_item_draft_action(arguments: dict, actor: str) -> dict:
|
|
190
191
|
client, _claims = create_authoring_client()
|
|
192
|
+
if arguments.get("newVersion") and arguments.get("id"):
|
|
193
|
+
existing = client.get_record("Item", arguments["id"])
|
|
194
|
+
if existing is None:
|
|
195
|
+
raise PublishError("not-found", "That story was not found.")
|
|
196
|
+
record = save_story_version(
|
|
197
|
+
client,
|
|
198
|
+
existing.get("lineageId") or existing["id"],
|
|
199
|
+
front_matter_yaml=arguments.get("frontMatterYaml"),
|
|
200
|
+
body_markus=arguments.get("bodyMarkus") or "",
|
|
201
|
+
actor=actor,
|
|
202
|
+
change_reason=arguments.get("changeReason") or "edited in the newsroom",
|
|
203
|
+
)
|
|
204
|
+
return {
|
|
205
|
+
"ok": True,
|
|
206
|
+
"item": {
|
|
207
|
+
"id": record["id"],
|
|
208
|
+
"contentHash": record["contentHash"],
|
|
209
|
+
"status": record["status"],
|
|
210
|
+
"slug": record["slug"],
|
|
211
|
+
"versionNumber": record.get("versionNumber"),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
191
214
|
fields = ItemFields(
|
|
192
215
|
type=arguments["type"],
|
|
193
216
|
slug=arguments["slug"],
|
|
@@ -69,9 +69,9 @@ export function slackAgentInstructions(): string {
|
|
|
69
69
|
"",
|
|
70
70
|
"Be concise, accurate, and concrete. Slack has no web UI: do not use papyrus.web.navigate, papyrus.web.current_location, or assume the user can see papyrus:// pages.",
|
|
71
71
|
"",
|
|
72
|
-
"Raw console chat turns are working memory and are excluded from default semantic searches unless explicitly requested. When a chat produces durable
|
|
72
|
+
"Raw console chat turns are working memory and are excluded from default semantic searches unless explicitly requested. When a chat produces durable note, recommend creating a note Message instead of making every chat turn canonical knowledge.",
|
|
73
73
|
"",
|
|
74
|
-
"Respond openly to questions, commands, and discussion—the same conversational stance as the web console. Do not treat every message as a citation submission. Register references or create
|
|
74
|
+
"Respond openly to questions, commands, and discussion—the same conversational stance as the web console. Do not treat every message as a citation submission. Register references or create notes only when the user shares URLs/DOIs or asks you to file, summarize, or comment on specific material.",
|
|
75
75
|
"",
|
|
76
76
|
'For requests like "most recent references" or "tell me about recent references", do not ask clarifying questions first: immediately call execute_tactus with a Reference.list snippet, then summarize the results.',
|
|
77
77
|
"",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Local preview only: derives a story's Markus body through the real Python engine so directives render.
|
|
9
|
+
* Set PAPYRUS_PREVIEW_PYTHON to a Python that has Papyrus and its Markus extra installed.
|
|
10
|
+
*/
|
|
11
|
+
export async function POST(request: Request) {
|
|
12
|
+
if (process.env.NEXT_PUBLIC_PAPYRUS_LOCAL_PREVIEW !== "1") return new NextResponse(null, { status: 404 });
|
|
13
|
+
const python = process.env.PAPYRUS_PREVIEW_PYTHON?.trim();
|
|
14
|
+
if (!python) return NextResponse.json({ error: "PAPYRUS_PREVIEW_PYTHON is not set." }, { status: 501 });
|
|
15
|
+
const input = await request.text();
|
|
16
|
+
const script = path.join(process.cwd(), "scripts", "local-preview-derive.py");
|
|
17
|
+
const output = await new Promise<string>((resolve, reject) => {
|
|
18
|
+
const child = spawn(python, [script], { env: process.env });
|
|
19
|
+
let stdout = "";
|
|
20
|
+
let stderr = "";
|
|
21
|
+
child.stdout.on("data", chunk => { stdout += chunk; });
|
|
22
|
+
child.stderr.on("data", chunk => { stderr += chunk; });
|
|
23
|
+
child.on("error", reject);
|
|
24
|
+
child.on("close", code => code === 0 ? resolve(stdout) : reject(new Error(stderr || `derive exited with ${code}`)));
|
|
25
|
+
child.stdin.end(input);
|
|
26
|
+
}).catch((reason: unknown) => JSON.stringify({ error: reason instanceof Error ? reason.message : "derive failed" }));
|
|
27
|
+
return new NextResponse(output, { headers: { "content-type": "application/json" } });
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
const CONTENT_TYPES: Record<string, string> = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", avif: "image/avif" };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local preview only: streams one story image from the publication's storage bucket so the previews show real figures.
|
|
10
|
+
* Set PAPYRUS_PREVIEW_MEDIA_BUCKET to the bucket; the server's AWS credentials do the reading.
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(request: Request) {
|
|
13
|
+
if (process.env.NEXT_PUBLIC_PAPYRUS_LOCAL_PREVIEW !== "1") return new NextResponse(null, { status: 404 });
|
|
14
|
+
const bucket = process.env.PAPYRUS_PREVIEW_MEDIA_BUCKET?.trim();
|
|
15
|
+
const key = new URL(request.url).searchParams.get("key") ?? "";
|
|
16
|
+
if (!bucket) return NextResponse.json({ error: "PAPYRUS_PREVIEW_MEDIA_BUCKET is not set." }, { status: 501 });
|
|
17
|
+
if (!/^media\/[A-Za-z0-9_\-./]+$/.test(key) || key.includes("..")) return new NextResponse(null, { status: 400 });
|
|
18
|
+
const extension = key.split(".").pop()?.toLowerCase() ?? "";
|
|
19
|
+
const child = spawn("aws", ["s3", "cp", `s3://${bucket}/${key}`, "-"], { env: process.env });
|
|
20
|
+
const chunks: Buffer[] = [];
|
|
21
|
+
child.stdout.on("data", chunk => chunks.push(chunk));
|
|
22
|
+
const exitCode = await new Promise<number>(resolve => { child.on("close", code => resolve(code ?? 1)); child.on("error", () => resolve(1)); });
|
|
23
|
+
if (exitCode !== 0) return new NextResponse(null, { status: 404 });
|
|
24
|
+
return new NextResponse(Buffer.concat(chunks), { headers: { "content-type": CONTENT_TYPES[extension] ?? "application/octet-stream", "cache-control": "private, max-age=300" } });
|
|
25
|
+
}
|
package/app/globals.css
CHANGED
|
@@ -6275,7 +6275,7 @@ img {
|
|
|
6275
6275
|
padding-top: calc(var(--paper-rhythm) - 1px);
|
|
6276
6276
|
}
|
|
6277
6277
|
|
|
6278
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-topic-workflow] + [data-reference-
|
|
6278
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-topic-workflow] + [data-reference-note-workflow] {
|
|
6279
6279
|
border-top: 0;
|
|
6280
6280
|
padding-top: 0;
|
|
6281
6281
|
}
|
|
@@ -6476,7 +6476,7 @@ img {
|
|
|
6476
6476
|
|
|
6477
6477
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__input button,
|
|
6478
6478
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__header > button,
|
|
6479
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6479
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6480
6480
|
background: transparent;
|
|
6481
6481
|
border: 1px solid rgb(var(--rule-rgb) / 0.42);
|
|
6482
6482
|
box-sizing: border-box;
|
|
@@ -6498,7 +6498,7 @@ img {
|
|
|
6498
6498
|
}
|
|
6499
6499
|
|
|
6500
6500
|
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-workflow__header > button:disabled,
|
|
6501
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6501
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button:disabled {
|
|
6502
6502
|
cursor: not-allowed;
|
|
6503
6503
|
opacity: 0.45;
|
|
6504
6504
|
}
|
|
@@ -6521,26 +6521,26 @@ img {
|
|
|
6521
6521
|
margin-right: calc(var(--paper-rhythm) * 0.5);
|
|
6522
6522
|
}
|
|
6523
6523
|
|
|
6524
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6524
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form {
|
|
6525
6525
|
display: grid;
|
|
6526
6526
|
gap: 0;
|
|
6527
6527
|
width: 100%;
|
|
6528
6528
|
}
|
|
6529
6529
|
|
|
6530
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6530
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell {
|
|
6531
6531
|
display: grid;
|
|
6532
6532
|
gap: 0;
|
|
6533
6533
|
}
|
|
6534
6534
|
|
|
6535
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6535
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > * + * {
|
|
6536
6536
|
margin-top: var(--paper-rhythm);
|
|
6537
6537
|
}
|
|
6538
6538
|
|
|
6539
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6539
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form > * + * {
|
|
6540
6540
|
margin-top: var(--paper-rhythm);
|
|
6541
6541
|
}
|
|
6542
6542
|
|
|
6543
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6543
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6544
6544
|
align-items: center;
|
|
6545
6545
|
appearance: none;
|
|
6546
6546
|
display: inline-flex;
|
|
@@ -6550,7 +6550,7 @@ img {
|
|
|
6550
6550
|
vertical-align: top;
|
|
6551
6551
|
}
|
|
6552
6552
|
|
|
6553
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6553
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header > button {
|
|
6554
6554
|
align-items: center;
|
|
6555
6555
|
appearance: none;
|
|
6556
6556
|
display: inline-flex;
|
|
@@ -6562,37 +6562,37 @@ img {
|
|
|
6562
6562
|
vertical-align: top;
|
|
6563
6563
|
}
|
|
6564
6564
|
|
|
6565
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6565
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header {
|
|
6566
6566
|
align-items: flex-start;
|
|
6567
6567
|
}
|
|
6568
6568
|
|
|
6569
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6569
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list {
|
|
6570
6570
|
display: grid;
|
|
6571
6571
|
gap: 0;
|
|
6572
6572
|
}
|
|
6573
6573
|
|
|
6574
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6574
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row {
|
|
6575
6575
|
border-left: 0;
|
|
6576
6576
|
gap: 0;
|
|
6577
6577
|
padding-left: 0;
|
|
6578
6578
|
}
|
|
6579
6579
|
|
|
6580
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6580
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row + .news-desk-reference-topic-state-row {
|
|
6581
6581
|
margin-top: var(--paper-rhythm);
|
|
6582
6582
|
}
|
|
6583
6583
|
|
|
6584
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6585
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6584
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row__headline,
|
|
6585
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row__meta {
|
|
6586
6586
|
gap: 0;
|
|
6587
6587
|
}
|
|
6588
6588
|
|
|
6589
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6590
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6589
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row strong,
|
|
6590
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-list .news-desk-reference-topic-state-row span {
|
|
6591
6591
|
line-height: var(--paper-rhythm);
|
|
6592
6592
|
min-height: var(--paper-rhythm);
|
|
6593
6593
|
}
|
|
6594
6594
|
|
|
6595
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6595
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note {
|
|
6596
6596
|
display: grid;
|
|
6597
6597
|
gap: 0;
|
|
6598
6598
|
grid-template-rows: var(--paper-rhythm) auto;
|
|
@@ -6600,7 +6600,7 @@ img {
|
|
|
6600
6600
|
min-width: 0;
|
|
6601
6601
|
}
|
|
6602
6602
|
|
|
6603
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6603
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note span {
|
|
6604
6604
|
color: var(--muted-strong);
|
|
6605
6605
|
font-family: Arial, Helvetica, sans-serif;
|
|
6606
6606
|
font-size: 0.66rem;
|
|
@@ -6611,13 +6611,13 @@ img {
|
|
|
6611
6611
|
text-transform: uppercase;
|
|
6612
6612
|
}
|
|
6613
6613
|
|
|
6614
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6614
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell button {
|
|
6615
6615
|
line-height: var(--paper-rhythm);
|
|
6616
6616
|
min-height: var(--news-desk-detail-control-height);
|
|
6617
6617
|
}
|
|
6618
6618
|
|
|
6619
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6620
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6619
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note input,
|
|
6620
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form .news-desk-reference-curation-note textarea {
|
|
6621
6621
|
box-sizing: border-box;
|
|
6622
6622
|
display: block;
|
|
6623
6623
|
font-family: Arial, Helvetica, sans-serif;
|
|
@@ -6627,7 +6627,7 @@ img {
|
|
|
6627
6627
|
padding: 0 calc(var(--paper-rhythm) * 0.5);
|
|
6628
6628
|
}
|
|
6629
6629
|
|
|
6630
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6630
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form textarea {
|
|
6631
6631
|
background: rgb(var(--paper-rgb) / 0.42);
|
|
6632
6632
|
border: 1px solid rgb(var(--rule-rgb) / 0.32);
|
|
6633
6633
|
color: var(--ink);
|
|
@@ -6637,7 +6637,7 @@ img {
|
|
|
6637
6637
|
resize: vertical;
|
|
6638
6638
|
}
|
|
6639
6639
|
|
|
6640
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6640
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions {
|
|
6641
6641
|
align-items: center;
|
|
6642
6642
|
column-gap: calc(var(--paper-rhythm) * 0.5);
|
|
6643
6643
|
display: flex;
|
|
@@ -6646,7 +6646,7 @@ img {
|
|
|
6646
6646
|
min-height: var(--paper-rhythm);
|
|
6647
6647
|
}
|
|
6648
6648
|
|
|
6649
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6649
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions button {
|
|
6650
6650
|
background: transparent;
|
|
6651
6651
|
border: 1px solid rgb(var(--rule-rgb) / 0.42);
|
|
6652
6652
|
box-sizing: border-box;
|
|
@@ -6662,7 +6662,7 @@ img {
|
|
|
6662
6662
|
text-transform: uppercase;
|
|
6663
6663
|
}
|
|
6664
6664
|
|
|
6665
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6665
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-form__actions button:disabled {
|
|
6666
6666
|
cursor: not-allowed;
|
|
6667
6667
|
opacity: 0.45;
|
|
6668
6668
|
}
|
|
@@ -6895,11 +6895,11 @@ img {
|
|
|
6895
6895
|
justify-self: stretch;
|
|
6896
6896
|
}
|
|
6897
6897
|
|
|
6898
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-
|
|
6898
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] .news-desk-reference-note-shell > button {
|
|
6899
6899
|
justify-self: stretch;
|
|
6900
6900
|
}
|
|
6901
6901
|
|
|
6902
|
-
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-
|
|
6902
|
+
.newsroom-list-detail-shell [data-news-desk-reference-detail] [data-reference-note-workflow] .news-desk-reference-workflow__header {
|
|
6903
6903
|
align-items: flex-start;
|
|
6904
6904
|
flex-direction: row;
|
|
6905
6905
|
gap: var(--paper-rhythm);
|
package/app/layout.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { AmplifyClientProvider } from "../components/amplify-client-provider";
|
|
|
5
5
|
import { PapyrusConsoleShell } from "../components/papyrus-console-shell";
|
|
6
6
|
import { TooltipProvider } from "../components/ui/tooltip";
|
|
7
7
|
import { resolveBrandFontAssets } from "../lib/brand-fonts";
|
|
8
|
-
import { getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
8
|
+
import { DEFAULT_SITE_BRAND_ICONS, getPresentationChoices, getSiteBrand } from "../lib/site-brand";
|
|
9
9
|
import { getSiteStack } from "../lib/site-stack";
|
|
10
10
|
import { isIndexable, showStagingBanner } from "../lib/site-env";
|
|
11
11
|
import { StagingBanner } from "../components/staging-banner";
|
|
@@ -34,9 +34,10 @@ export async function generateMetadata(): Promise<Metadata> {
|
|
|
34
34
|
...(isIndexable() ? {} : { robots: { index: false, follow: false } }),
|
|
35
35
|
icons: {
|
|
36
36
|
icon: [
|
|
37
|
-
{ url:
|
|
38
|
-
{ url:
|
|
37
|
+
{ url: `${siteBrand.icons?.light ?? DEFAULT_SITE_BRAND_ICONS.light}?v=${faviconVersion}`, type: "image/png" },
|
|
38
|
+
...(siteBrand.icons ? [] : [{ url: `${DEFAULT_SITE_BRAND_ICONS.fallback}?v=${faviconVersion}`, type: "image/png" }]),
|
|
39
39
|
],
|
|
40
|
+
...(siteBrand.icons?.appleTouch ? { apple: [{ url: `${siteBrand.icons.appleTouch}?v=${faviconVersion}`, type: "image/png" }] } : {}),
|
|
40
41
|
},
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -46,6 +47,8 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
|
|
|
46
47
|
const presentationChoices = getPresentationChoices(siteBrand);
|
|
47
48
|
const siteStack = getSiteStack(siteBrand);
|
|
48
49
|
const brandFonts = resolveBrandFontAssets(siteBrand.fonts);
|
|
50
|
+
const lightIconPath = siteBrand.icons?.light ?? DEFAULT_SITE_BRAND_ICONS.light;
|
|
51
|
+
const darkIconPath = siteBrand.icons ? siteBrand.icons.dark ?? siteBrand.icons.light : DEFAULT_SITE_BRAND_ICONS.dark;
|
|
49
52
|
|
|
50
53
|
return (
|
|
51
54
|
<html
|
|
@@ -78,8 +81,8 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
|
|
|
78
81
|
<Script id="papyrus-favicon-color-scheme" strategy="beforeInteractive">
|
|
79
82
|
{`(() => {
|
|
80
83
|
const version = "${faviconVersion}";
|
|
81
|
-
const lightHref = "
|
|
82
|
-
const darkHref = "
|
|
84
|
+
const lightHref = ${JSON.stringify(lightIconPath)} + "?v=" + version;
|
|
85
|
+
const darkHref = ${JSON.stringify(darkIconPath)} + "?v=" + version;
|
|
83
86
|
const settingsStorageKey = "papyrus:reader-settings";
|
|
84
87
|
const defaultTheme = "${defaultTheme}";
|
|
85
88
|
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permanentRedirect } from "next/navigation";
|
|
2
|
+
import { toPublicNewsroomPath } from "../../../../lib/newsroom-base-path";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type NewsroomInsightThreadPageProps = {
|
|
4
|
+
type NewsroomMessageThreadRedirectPageProps = {
|
|
6
5
|
params: Promise<{ threadId: string }>;
|
|
7
|
-
searchParams?: Promise<{
|
|
8
|
-
demo?: string | string[];
|
|
9
|
-
domain?: string | string[];
|
|
10
|
-
}>;
|
|
11
6
|
};
|
|
12
7
|
|
|
13
|
-
export default async function
|
|
8
|
+
export default async function NewsroomMessageThreadRedirectPage({ params }: NewsroomMessageThreadRedirectPageProps) {
|
|
14
9
|
const { threadId } = await params;
|
|
15
|
-
|
|
16
|
-
<NewsDeskPage
|
|
17
|
-
section="insights"
|
|
18
|
-
selectionPath={[threadId]}
|
|
19
|
-
searchParams={searchParams}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
10
|
+
permanentRedirect(toPublicNewsroomPath(`/newsroom/messages/${encodeURIComponent(threadId)}`));
|
|
22
11
|
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permanentRedirect } from "next/navigation";
|
|
2
|
+
import { toPublicNewsroomPath } from "../../../lib/newsroom-base-path";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
type NewsroomInsightsPageProps = {
|
|
6
|
-
searchParams?: Promise<{
|
|
7
|
-
demo?: string | string[];
|
|
8
|
-
domain?: string | string[];
|
|
9
|
-
}>;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default async function NewsroomInsightsPage({ searchParams }: NewsroomInsightsPageProps) {
|
|
13
|
-
return <NewsDeskPage section="insights" searchParams={searchParams} />;
|
|
4
|
+
export default function NewsroomMessagesRedirectPage() {
|
|
5
|
+
permanentRedirect(toPublicNewsroomPath("/newsroom/messages"));
|
|
14
6
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
type Excerpt = { issueId: string; text: string };
|
|
7
|
+
|
|
8
|
+
function CardExcerpt({ text }: { text: string }) {
|
|
9
|
+
const [expanded, setExpanded] = useState(false);
|
|
10
|
+
return <div className="board-card-excerpt" data-board-card-excerpt data-expanded={expanded}>
|
|
11
|
+
<p>{text}</p>
|
|
12
|
+
{text.length > 90 && <button aria-expanded={expanded} onClick={event => { event.stopPropagation(); setExpanded(open => !open); }} onKeyDown={event => event.stopPropagation()} type="button">{expanded ? "Less" : "More"}</button>}
|
|
13
|
+
</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds a short expandable excerpt under each card's headline. The Kanbus card has no excerpt slot,
|
|
18
|
+
* so each excerpt is rendered into the card's content block once the card is on the page.
|
|
19
|
+
*/
|
|
20
|
+
export function BoardCardExcerpts({ excerpts, scope }: { excerpts: readonly Excerpt[]; scope: HTMLElement | null }) {
|
|
21
|
+
const [hosts, setHosts] = useState<{ host: HTMLElement; text: string }[]>([]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!scope) return;
|
|
24
|
+
const collect = () => {
|
|
25
|
+
const found = excerpts.flatMap(({ issueId, text }) => {
|
|
26
|
+
const card = Array.from(scope.querySelectorAll<HTMLElement>(".issue-card")).find(candidate => candidate.dataset.issueId === issueId);
|
|
27
|
+
const content = card?.querySelector<HTMLElement>(".issue-card-content");
|
|
28
|
+
if (!content) return [];
|
|
29
|
+
let host = content.querySelector<HTMLElement>(":scope > [data-board-card-excerpt-host]");
|
|
30
|
+
if (!host) {
|
|
31
|
+
host = document.createElement("div");
|
|
32
|
+
host.dataset.boardCardExcerptHost = "true";
|
|
33
|
+
content.querySelector("h3")?.after(host);
|
|
34
|
+
}
|
|
35
|
+
return [{ host, text }];
|
|
36
|
+
});
|
|
37
|
+
setHosts(previous => previous.length === found.length && previous.every((entry, index) => entry.host === found[index].host && entry.text === found[index].text) ? previous : found);
|
|
38
|
+
};
|
|
39
|
+
collect();
|
|
40
|
+
const observer = new MutationObserver(collect);
|
|
41
|
+
observer.observe(scope, { childList: true, subtree: true });
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, [excerpts, scope]);
|
|
44
|
+
return <>{hosts.map(({ host, text }, index) => createPortal(<CardExcerpt text={text} />, host, `${index}`))}</>;
|
|
45
|
+
}
|