@anthusai/papyrus 5.43.0-next.43 → 5.43.0-next.46
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 +8 -1
- package/amplify/data/resource.ts +8 -1
- package/amplify/data/schema.js +8 -1
- package/amplify/data/schema.ts +8 -1
- package/components/news-desk-taxonomy-client.ts +10 -1
- package/components/story-reporting-trail-client.ts +4 -3
- package/components/story-reporting-trail.tsx +8 -0
- package/lib/story-reporting-trail.ts +28 -2
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
package/amplify/data/resource.js
CHANGED
|
@@ -1670,6 +1670,9 @@ const schema = a.schema({
|
|
|
1670
1670
|
authorSub: a.string(),
|
|
1671
1671
|
authorUserProfileId: a.id(),
|
|
1672
1672
|
authorLabel: a.string(),
|
|
1673
|
+
editedAt: a.datetime(),
|
|
1674
|
+
deletedAt: a.datetime(),
|
|
1675
|
+
deletedBySub: a.string(),
|
|
1673
1676
|
threadId: a.id(),
|
|
1674
1677
|
parentMessageId: a.id(),
|
|
1675
1678
|
sequenceNumber: a.integer(),
|
|
@@ -1704,8 +1707,12 @@ const schema = a.schema({
|
|
|
1704
1707
|
index("searchVisibility").sortKeys(["createdAt"]).name("messagesBySearchVisibilityCreatedAt").queryField("listMessagesBySearchVisibilityAndCreatedAt"),
|
|
1705
1708
|
])
|
|
1706
1709
|
.authorization((allow) => [
|
|
1707
|
-
|
|
1710
|
+
// One rule per group (a group may appear once per model): editors read and add; admins also delete, for moderation.
|
|
1711
|
+
allow.group("editor").to(categoryAppendOnlyOperations),
|
|
1712
|
+
allow.group(adminGroup).to(["read", "create", "delete"]),
|
|
1708
1713
|
allow.authenticated("identityPool").to(["read", "create", "update"]),
|
|
1714
|
+
// Messages belong to their author: the author edits (and soft-deletes) their own, and only an admin can remove someone else's.
|
|
1715
|
+
allow.ownerDefinedIn("authorSub").identityClaim("sub").to(["read", "update", "delete"]),
|
|
1709
1716
|
]),
|
|
1710
1717
|
MessageThread: a
|
|
1711
1718
|
.model({
|
package/amplify/data/resource.ts
CHANGED
|
@@ -1786,6 +1786,9 @@ const schema = a.schema({
|
|
|
1786
1786
|
authorSub: a.string(),
|
|
1787
1787
|
authorUserProfileId: a.id(),
|
|
1788
1788
|
authorLabel: a.string(),
|
|
1789
|
+
editedAt: a.datetime(),
|
|
1790
|
+
deletedAt: a.datetime(),
|
|
1791
|
+
deletedBySub: a.string(),
|
|
1789
1792
|
threadId: a.id(),
|
|
1790
1793
|
parentMessageId: a.id(),
|
|
1791
1794
|
sequenceNumber: a.integer(),
|
|
@@ -1820,8 +1823,12 @@ const schema = a.schema({
|
|
|
1820
1823
|
index("searchVisibility").sortKeys(["createdAt"]).name("messagesBySearchVisibilityCreatedAt").queryField("listMessagesBySearchVisibilityAndCreatedAt"),
|
|
1821
1824
|
])
|
|
1822
1825
|
.authorization((allow) => [
|
|
1823
|
-
|
|
1826
|
+
// One rule per group (a group may appear once per model): editors read and add; admins also delete, for moderation.
|
|
1827
|
+
allow.group("editor").to(categoryAppendOnlyOperations),
|
|
1828
|
+
allow.group(adminGroup).to(["read", "create", "delete"]),
|
|
1824
1829
|
allow.authenticated("identityPool").to(["read", "create", "update"]),
|
|
1830
|
+
// Messages belong to their author: the author edits (and soft-deletes) their own, and only an admin can remove someone else's.
|
|
1831
|
+
allow.ownerDefinedIn("authorSub").identityClaim("sub").to(["read", "update", "delete"]),
|
|
1825
1832
|
]),
|
|
1826
1833
|
|
|
1827
1834
|
MessageThread: a
|
package/amplify/data/schema.js
CHANGED
|
@@ -1573,6 +1573,9 @@ function buildBaseDefinitions(includeEditions) {
|
|
|
1573
1573
|
authorSub: a.string(),
|
|
1574
1574
|
authorUserProfileId: a.id(),
|
|
1575
1575
|
authorLabel: a.string(),
|
|
1576
|
+
editedAt: a.datetime(),
|
|
1577
|
+
deletedAt: a.datetime(),
|
|
1578
|
+
deletedBySub: a.string(),
|
|
1576
1579
|
threadId: a.id(),
|
|
1577
1580
|
parentMessageId: a.id(),
|
|
1578
1581
|
sequenceNumber: a.integer(),
|
|
@@ -1607,7 +1610,11 @@ function buildBaseDefinitions(includeEditions) {
|
|
|
1607
1610
|
index("searchVisibility").sortKeys(["createdAt"]).name("messagesBySearchVisibilityCreatedAt").queryField("listMessagesBySearchVisibilityAndCreatedAt"),
|
|
1608
1611
|
])
|
|
1609
1612
|
.authorization((allow) => [
|
|
1610
|
-
|
|
1613
|
+
// One rule per group (a group may appear once per model): editors read and add; admins also delete, for moderation.
|
|
1614
|
+
allow.group("editor").to(categoryAppendOnlyOperations),
|
|
1615
|
+
allow.group(adminGroup).to(["read", "create", "delete"]),
|
|
1616
|
+
// Messages belong to their author: the author edits (and soft-deletes) their own, and only an admin can remove someone else's.
|
|
1617
|
+
allow.ownerDefinedIn("authorSub").identityClaim("sub").to(["read", "update", "delete"]),
|
|
1611
1618
|
]),
|
|
1612
1619
|
MessageThread: realtimeModel({
|
|
1613
1620
|
id: a.id().required(),
|
package/amplify/data/schema.ts
CHANGED
|
@@ -1687,6 +1687,9 @@ const definitions = {
|
|
|
1687
1687
|
authorSub: a.string(),
|
|
1688
1688
|
authorUserProfileId: a.id(),
|
|
1689
1689
|
authorLabel: a.string(),
|
|
1690
|
+
editedAt: a.datetime(),
|
|
1691
|
+
deletedAt: a.datetime(),
|
|
1692
|
+
deletedBySub: a.string(),
|
|
1690
1693
|
threadId: a.id(),
|
|
1691
1694
|
parentMessageId: a.id(),
|
|
1692
1695
|
sequenceNumber: a.integer(),
|
|
@@ -1721,7 +1724,11 @@ const definitions = {
|
|
|
1721
1724
|
index("searchVisibility").sortKeys(["createdAt"]).name("messagesBySearchVisibilityCreatedAt").queryField("listMessagesBySearchVisibilityAndCreatedAt"),
|
|
1722
1725
|
])
|
|
1723
1726
|
.authorization((allow) => [
|
|
1724
|
-
|
|
1727
|
+
// One rule per group (a group may appear once per model): editors read and add; admins also delete, for moderation.
|
|
1728
|
+
allow.group("editor").to(categoryAppendOnlyOperations),
|
|
1729
|
+
allow.group(adminGroup).to(["read", "create", "delete"]),
|
|
1730
|
+
// Messages belong to their author: the author edits (and soft-deletes) their own, and only an admin can remove someone else's.
|
|
1731
|
+
allow.ownerDefinedIn("authorSub").identityClaim("sub").to(["read", "update", "delete"]),
|
|
1725
1732
|
]),
|
|
1726
1733
|
|
|
1727
1734
|
MessageThread: realtimeModel({
|
|
@@ -1431,7 +1431,9 @@ async function appendMessageToThread(thread: MessageThreadRecord, build: (sequen
|
|
|
1431
1431
|
list: () => listMessagesByThreadId(threadId, 1000),
|
|
1432
1432
|
create: (sequenceNumber) => {
|
|
1433
1433
|
now = new Date().toISOString();
|
|
1434
|
-
|
|
1434
|
+
// `createdAt` is part of the composite sort key of messagesByResponseTargetStatusCreatedAt, so AppSync refuses a Message that
|
|
1435
|
+
// sets it without `responseStatus`. A person's message waits on no responder: COMPLETED, as the Python message contract writes.
|
|
1436
|
+
built = { responseStatus: "COMPLETED", ...build(sequenceNumber, now) };
|
|
1435
1437
|
return runGraphql<{ createMessage?: MessageRecord | null }>(CREATE_MESSAGE_MUTATION, { input: built });
|
|
1436
1438
|
},
|
|
1437
1439
|
});
|
|
@@ -1905,10 +1907,17 @@ async function runGraphql<T extends Record<string, unknown>>(
|
|
|
1905
1907
|
const client = generateClient<Schema>() as unknown as {
|
|
1906
1908
|
graphql: (options: Record<string, unknown>) => Promise<{ data?: T | null; errors?: unknown[] | null }>;
|
|
1907
1909
|
};
|
|
1910
|
+
// The Amplify client rejects with the raw `{ data, errors }` response when the API returns errors; turn that into an
|
|
1911
|
+
// Error carrying the API's own words, so a refusal is said in words instead of as an unknown failure.
|
|
1908
1912
|
const response = await client.graphql({
|
|
1909
1913
|
query,
|
|
1910
1914
|
variables,
|
|
1911
1915
|
authMode: USER_POOL_AUTH_MODE,
|
|
1916
|
+
}).catch((failure: unknown) => {
|
|
1917
|
+
if (failure && typeof failure === "object" && Array.isArray((failure as { errors?: unknown }).errors)) {
|
|
1918
|
+
assertNoGraphQLErrors((failure as { errors: unknown[] }).errors);
|
|
1919
|
+
}
|
|
1920
|
+
throw failure;
|
|
1912
1921
|
});
|
|
1913
1922
|
assertNoGraphQLErrors(response.errors);
|
|
1914
1923
|
return (response.data ?? {}) as T;
|
|
@@ -4,7 +4,7 @@ import { generateClient } from "aws-amplify/data";
|
|
|
4
4
|
import type { Schema } from "../amplify/data/resource";
|
|
5
5
|
import { isLocalPreviewEnabled } from "../lib/local-preview";
|
|
6
6
|
import { createLocalPreviewReportingTrail, createLocalPreviewStoriesForReference } from "../lib/local-preview-seed";
|
|
7
|
-
import { EMPTY_STORY_REPORTING_TRAIL, referencesByRole, siteNameOf, type ReferenceStory, type StoryReportingTrail, type TrailAssignment, type TrailAttachment, type TrailReference, type TrailThread, type TrailVersion } from "../lib/story-reporting-trail";
|
|
7
|
+
import { EMPTY_STORY_REPORTING_TRAIL, referencesByRole, siteNameOf, stageMovesOf, type ReferenceStory, type StoryReportingTrail, type TrailAssignment, type TrailAttachment, type TrailReference, type TrailThread, type TrailVersion } from "../lib/story-reporting-trail";
|
|
8
8
|
import { configureAmplifyClient } from "./amplify-client-provider";
|
|
9
9
|
|
|
10
10
|
type GraphqlResponse = { data?: Record<string, { items?: Record<string, unknown>[] | null; nextToken?: string | null } | null> | null; errors?: { message?: string }[] | null };
|
|
@@ -89,7 +89,7 @@ export async function loadStoryReportingTrail(lineageId: string, itemId: string,
|
|
|
89
89
|
configureAmplifyClient();
|
|
90
90
|
const client = generateClient<Schema>() as unknown as GraphqlClient;
|
|
91
91
|
const [versionRows, assignmentRows, attachmentRows, relationRows] = await Promise.all([
|
|
92
|
-
listAll(client, "listItemsByLineageAndVersion", "id versionNumber versionState status stage headline title changeReason versionCreatedBy versionCreatedAt updatedAt", ", $lineageId: ID!", ", lineageId: $lineageId", { lineageId }).catch(() => []),
|
|
92
|
+
listAll(client, "listItemsByLineageAndVersion", "id versionNumber versionState status stage headline title changeReason versionCreatedBy versionCreatedAt updatedAt metadata", ", $lineageId: ID!", ", lineageId: $lineageId", { lineageId }).catch(() => []),
|
|
93
93
|
listAll(client, "listAssignmentsByItemLineageAndCreatedAt", "id title assignmentTypeKey status assigneeKey summary createdAt", ", $itemLineageId: ID!", ", itemLineageId: $itemLineageId", { itemLineageId: lineageId }).catch(() => []),
|
|
94
94
|
listAll(client, "listModelAttachmentsByOwnerRoleAndSortKey", "id role filename mediaType byteSize", ", $ownerId: ID!", ", ownerId: $ownerId", { ownerId: itemId }).catch(() => []),
|
|
95
95
|
listAll(client, "listSemanticRelations", "id predicate subjectKind subjectLineageId objectKind objectId objectLineageId metadata", ", $filter: ModelSemanticRelationFilterInput", ", filter: $filter", { filter: { or: [{ subjectLineageId: { eq: lineageId } }, { objectLineageId: { eq: lineageId } }] } }).catch(() => []),
|
|
@@ -124,5 +124,6 @@ export async function loadStoryReportingTrail(lineageId: string, itemId: string,
|
|
|
124
124
|
};
|
|
125
125
|
})));
|
|
126
126
|
const attachments: TrailAttachment[] = attachmentRows.map(row => ({ id: String(row.id), role: text(row.role), filename: text(row.filename), mediaType: text(row.mediaType), byteSize: typeof row.byteSize === "number" ? row.byteSize : null }));
|
|
127
|
-
|
|
127
|
+
const latestRow = [...versionRows].sort((left, right) => Number(right.versionNumber ?? 0) - Number(left.versionNumber ?? 0))[0];
|
|
128
|
+
return { ...EMPTY_STORY_REPORTING_TRAIL, versions, assignments, threads, references, attachments, stageMoves: stageMovesOf(latestRow?.metadata) };
|
|
128
129
|
}
|
|
@@ -91,6 +91,14 @@ export function StoryReportingTrailView({ citedKeys = [], demo = false, focusedR
|
|
|
91
91
|
</li>)}
|
|
92
92
|
</ol>
|
|
93
93
|
</TrailSection>
|
|
94
|
+
{trail.stageMoves.length > 0 && <TrailSection count={trail.stageMoves.length} title="Stage moves">
|
|
95
|
+
<ol className="story-trail-list" data-story-trail-stage-moves>
|
|
96
|
+
{[...trail.stageMoves].reverse().map(move => <li key={`${move.at}-${move.to}`}>
|
|
97
|
+
<div className="story-trail-row"><strong>{move.from ?? "start"} → {move.to}</strong>{move.by && <span className="story-trail-chip">{move.by}</span>}<span className="story-trail-meta">{formatTimestamp(move.at)}</span></div>
|
|
98
|
+
{move.reason && <p className="story-trail-meta">{move.reason}</p>}
|
|
99
|
+
</li>)}
|
|
100
|
+
</ol>
|
|
101
|
+
</TrailSection>}
|
|
94
102
|
<TrailSection count={trail.versions.length} title="Draft versions">
|
|
95
103
|
<ol className="story-trail-list">
|
|
96
104
|
{trail.versions.map((version, versionIndex) => <li key={version.id}>
|
|
@@ -17,6 +17,15 @@ export type TrailVersion = {
|
|
|
17
17
|
updatedAt: string | null;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/** One stage move of the story, read from the latest Item version's `metadata.stageMoves` (not from Messages). */
|
|
21
|
+
export type TrailStageMove = {
|
|
22
|
+
at: string;
|
|
23
|
+
from: string | null;
|
|
24
|
+
to: string;
|
|
25
|
+
by: string | null;
|
|
26
|
+
reason: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
20
29
|
export type TrailAssignmentEvent = {
|
|
21
30
|
id: string;
|
|
22
31
|
eventType: string;
|
|
@@ -89,12 +98,29 @@ export type StoryReportingTrail = {
|
|
|
89
98
|
threads: TrailThread[];
|
|
90
99
|
references: TrailReference[];
|
|
91
100
|
attachments: TrailAttachment[];
|
|
101
|
+
stageMoves: TrailStageMove[];
|
|
92
102
|
};
|
|
93
103
|
|
|
94
|
-
export const EMPTY_STORY_REPORTING_TRAIL: StoryReportingTrail = { versions: [], assignments: [], threads: [], references: [], attachments: [] };
|
|
104
|
+
export const EMPTY_STORY_REPORTING_TRAIL: StoryReportingTrail = { versions: [], assignments: [], threads: [], references: [], attachments: [], stageMoves: [] };
|
|
105
|
+
|
|
106
|
+
/** The stage moves recorded on an Item version's metadata, oldest first. Anything that is not a well-formed move is skipped. */
|
|
107
|
+
export function stageMovesOf(metadata: unknown): TrailStageMove[] {
|
|
108
|
+
let parsed: unknown = metadata;
|
|
109
|
+
if (typeof metadata === "string") {
|
|
110
|
+
try { parsed = JSON.parse(metadata); } catch { return []; }
|
|
111
|
+
}
|
|
112
|
+
const moves = typeof parsed === "object" && parsed ? (parsed as Record<string, unknown>).stageMoves : null;
|
|
113
|
+
if (!Array.isArray(moves)) return [];
|
|
114
|
+
return moves.flatMap(entry => {
|
|
115
|
+
if (typeof entry !== "object" || !entry) return [];
|
|
116
|
+
const move = entry as Record<string, unknown>;
|
|
117
|
+
if (typeof move.at !== "string" || typeof move.to !== "string") return [];
|
|
118
|
+
return [{ at: move.at, from: typeof move.from === "string" ? move.from : null, to: move.to, by: typeof move.by === "string" ? move.by : null, reason: typeof move.reason === "string" ? move.reason : null }];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
95
121
|
|
|
96
122
|
export function storyReportingTrailIsEmpty(trail: StoryReportingTrail): boolean {
|
|
97
|
-
return trail.assignments.length === 0 && trail.threads.length === 0 && trail.references.length === 0 && trail.attachments.length === 0 && trail.versions.length <= 1;
|
|
123
|
+
return trail.assignments.length === 0 && trail.threads.length === 0 && trail.stageMoves.length === 0 && trail.references.length === 0 && trail.attachments.length === 0 && trail.versions.length <= 1;
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
export type StoryFeedEntry =
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED