@rightkit/social-ui 0.0.0-stage → 0.1.0

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 (124) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +40 -2
  5. package/dist/appName.d.ts +2 -0
  6. package/dist/appName.js +8 -0
  7. package/dist/components/ScopeSelect.d.ts +7 -0
  8. package/dist/components/ScopeSelect.js +6 -0
  9. package/dist/components/Shared.d.ts +34 -0
  10. package/dist/components/Shared.js +55 -0
  11. package/dist/data/derive.d.ts +21 -0
  12. package/dist/data/derive.js +47 -0
  13. package/dist/data/draftRules.d.ts +10 -0
  14. package/dist/data/draftRules.js +20 -0
  15. package/dist/data/engine/engineDataSource.d.ts +194 -0
  16. package/dist/data/engine/engineDataSource.js +836 -0
  17. package/dist/data/engine/errors.d.ts +13 -0
  18. package/dist/data/engine/errors.js +21 -0
  19. package/dist/data/engine/mapping.d.ts +172 -0
  20. package/dist/data/engine/mapping.js +1487 -0
  21. package/dist/data/engine/protocol.d.ts +910 -0
  22. package/dist/data/engine/protocol.js +67 -0
  23. package/dist/data/index.d.ts +18 -0
  24. package/dist/data/index.js +23 -0
  25. package/dist/data/mock/devSource.d.ts +7 -0
  26. package/dist/data/mock/devSource.js +10 -0
  27. package/dist/data/mock/fixtures-analytics.d.ts +7 -0
  28. package/dist/data/mock/fixtures-analytics.js +123 -0
  29. package/dist/data/mock/fixtures-briefs.d.ts +4 -0
  30. package/dist/data/mock/fixtures-briefs.js +230 -0
  31. package/dist/data/mock/fixtures-content.d.ts +4 -0
  32. package/dist/data/mock/fixtures-content.js +89 -0
  33. package/dist/data/mock/fixtures-digests.d.ts +3 -0
  34. package/dist/data/mock/fixtures-digests.js +40 -0
  35. package/dist/data/mock/fixtures-drafts.d.ts +2 -0
  36. package/dist/data/mock/fixtures-drafts.js +74 -0
  37. package/dist/data/mock/fixtures-gate.d.ts +3 -0
  38. package/dist/data/mock/fixtures-gate.js +49 -0
  39. package/dist/data/mock/fixtures-hooks.d.ts +2 -0
  40. package/dist/data/mock/fixtures-hooks.js +103 -0
  41. package/dist/data/mock/fixtures-inbox.d.ts +11 -0
  42. package/dist/data/mock/fixtures-inbox.js +213 -0
  43. package/dist/data/mock/fixtures-links.d.ts +5 -0
  44. package/dist/data/mock/fixtures-links.js +89 -0
  45. package/dist/data/mock/fixtures-memory.d.ts +3 -0
  46. package/dist/data/mock/fixtures-memory.js +148 -0
  47. package/dist/data/mock/fixtures-ops.d.ts +15 -0
  48. package/dist/data/mock/fixtures-ops.js +403 -0
  49. package/dist/data/mock/fixtures-optimise.d.ts +18 -0
  50. package/dist/data/mock/fixtures-optimise.js +221 -0
  51. package/dist/data/mock/fixtures-pacing.d.ts +5 -0
  52. package/dist/data/mock/fixtures-pacing.js +285 -0
  53. package/dist/data/mock/fixtures-publish.d.ts +2 -0
  54. package/dist/data/mock/fixtures-publish.js +129 -0
  55. package/dist/data/mock/fixtures-store.d.ts +10 -0
  56. package/dist/data/mock/fixtures-store.js +13 -0
  57. package/dist/data/mock/fixtures-variants.d.ts +5 -0
  58. package/dist/data/mock/fixtures-variants.js +138 -0
  59. package/dist/data/mock/fixtures.d.ts +6 -0
  60. package/dist/data/mock/fixtures.js +420 -0
  61. package/dist/data/mock/mockDataSource.d.ts +98 -0
  62. package/dist/data/mock/mockDataSource.js +903 -0
  63. package/dist/data/money.d.ts +14 -0
  64. package/dist/data/money.js +36 -0
  65. package/dist/data/platforms.d.ts +12 -0
  66. package/dist/data/platforms.js +26 -0
  67. package/dist/data/source.d.ts +196 -0
  68. package/dist/data/source.js +1 -0
  69. package/dist/data/time.d.ts +23 -0
  70. package/dist/data/time.js +86 -0
  71. package/dist/data/types.d.ts +1092 -0
  72. package/dist/data/types.js +3 -0
  73. package/dist/env.d.ts +59 -0
  74. package/dist/hooks/useAsync.d.ts +19 -0
  75. package/dist/hooks/useAsync.js +25 -0
  76. package/dist/index.d.ts +56 -0
  77. package/dist/index.js +58 -0
  78. package/dist/lib/host.d.ts +21 -0
  79. package/dist/lib/host.js +32 -0
  80. package/dist/lib/shell.d.ts +156 -0
  81. package/dist/lib/shell.js +16 -0
  82. package/dist/screens/AccountsScreen.d.ts +8 -0
  83. package/dist/screens/AccountsScreen.js +100 -0
  84. package/dist/screens/AnalyticsScreen.d.ts +7 -0
  85. package/dist/screens/AnalyticsScreen.js +64 -0
  86. package/dist/screens/ApprovalsScreen.d.ts +10 -0
  87. package/dist/screens/ApprovalsScreen.js +144 -0
  88. package/dist/screens/BriefsScreen.d.ts +7 -0
  89. package/dist/screens/BriefsScreen.js +83 -0
  90. package/dist/screens/CalendarScreen.d.ts +10 -0
  91. package/dist/screens/CalendarScreen.js +143 -0
  92. package/dist/screens/CampaignsScreen.d.ts +7 -0
  93. package/dist/screens/CampaignsScreen.js +52 -0
  94. package/dist/screens/ChannelMemoryScreen.d.ts +7 -0
  95. package/dist/screens/ChannelMemoryScreen.js +109 -0
  96. package/dist/screens/ContentScreen.d.ts +7 -0
  97. package/dist/screens/ContentScreen.js +64 -0
  98. package/dist/screens/DigestsScreen.d.ts +7 -0
  99. package/dist/screens/DigestsScreen.js +53 -0
  100. package/dist/screens/DraftsScreen.d.ts +10 -0
  101. package/dist/screens/DraftsScreen.js +108 -0
  102. package/dist/screens/FailuresScreen.d.ts +8 -0
  103. package/dist/screens/FailuresScreen.js +111 -0
  104. package/dist/screens/HookTestsScreen.d.ts +7 -0
  105. package/dist/screens/HookTestsScreen.js +54 -0
  106. package/dist/screens/InboxScreen.d.ts +12 -0
  107. package/dist/screens/InboxScreen.js +76 -0
  108. package/dist/screens/LinksScreen.d.ts +7 -0
  109. package/dist/screens/LinksScreen.js +52 -0
  110. package/dist/screens/PacingScreen.d.ts +7 -0
  111. package/dist/screens/PacingScreen.js +88 -0
  112. package/dist/screens/PublishJobsScreen.d.ts +7 -0
  113. package/dist/screens/PublishJobsScreen.js +66 -0
  114. package/dist/screens/SuggestionsScreen.d.ts +8 -0
  115. package/dist/screens/SuggestionsScreen.js +94 -0
  116. package/dist/screens/TextGateScreen.d.ts +7 -0
  117. package/dist/screens/TextGateScreen.js +111 -0
  118. package/dist/screens/ThreadsScreen.d.ts +8 -0
  119. package/dist/screens/ThreadsScreen.js +113 -0
  120. package/dist/screens/TypedBriefsScreen.d.ts +7 -0
  121. package/dist/screens/TypedBriefsScreen.js +75 -0
  122. package/dist/screens/labels.d.ts +121 -0
  123. package/dist/screens/labels.js +272 -0
  124. package/package.json +56 -3
@@ -0,0 +1,111 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Badge, Button, EmptyState, Field, Input, PageShell, SkeletonRows, Table, TableBody, TableCaption, TableCell, TableHead, TableHeaderCell, TableRow } from "@rightkit/react-shell/react";
4
+ import { ScopeSelect } from "../components/ScopeSelect.js";
5
+ import { LoadError, MockBanner } from "../components/Shared.js";
6
+ import { useAsync } from "../hooks/useAsync.js";
7
+ import { formatMilli, groupDigits, instantLabel, } from "../data/index.js";
8
+ import { GATE_DISPOSITION_LABEL, GATE_VERDICT_LABEL } from "./labels.js";
9
+ export function TextGateScreen({ source, rev }) {
10
+ const scopes = useAsync(() => source.listScopes(), [source, rev]);
11
+ const [picked, setPicked] = useState(null);
12
+ const scopeList = scopes.value ?? [];
13
+ const scopeId = scopeList.find((s) => s.id === picked)?.id ?? scopeList[0]?.id ?? null;
14
+ return (_jsxs(PageShell, { title: "Text gate", subtitle: "Read only. Checks a script against the channel's gate policy and shows each rule's verdict, then the stored verdict records of a subject. A check stores nothing. Nothing here changes the policy, records a check or approves anything.", children: [source.kind === "mock" ? _jsx(MockBanner, {}) : null, scopes.state.status === "error" ? (_jsx(LoadError, { message: "Could not read the channels from the daemon.", onRetry: scopes.reload })) : scopes.value === null ? (_jsx(SkeletonRows, { label: "Loading channels", rows: 2 })) : scopeList.length === 0 || scopeId === null ? (_jsx(EmptyState, { title: "No channels yet", children: _jsx("p", { children: "The text gate is kept per channel once one is set up." }) })) : (_jsxs(_Fragment, { children: [_jsx(ScopeSelect, { scopes: scopeList, value: scopeId, onChange: (id) => setPicked(id) }), _jsx(GateView, { source: source, rev: rev, scopeId: scopeId }, scopeId)] }))] }));
15
+ }
16
+ function GateView({ source, rev, scopeId }) {
17
+ const policy = useAsync(() => source.gatePolicy(scopeId), [source, rev, scopeId]);
18
+ if (policy.state.status === "error") {
19
+ return _jsx(LoadError, { message: "Could not read the gate policy from the daemon.", onRetry: policy.reload });
20
+ }
21
+ if (policy.state.status === "loading")
22
+ return _jsx(SkeletonRows, { label: "Loading the gate policy", rows: 3 });
23
+ const current = policy.state.value;
24
+ return (_jsxs(_Fragment, { children: [_jsxs("section", { "aria-labelledby": "gate-policy-title", children: [_jsx("h2", { id: "gate-policy-title", className: "op-section-title", children: "Gate policy" }), current === null ? (_jsx("p", { className: "op-hint", children: "No policy is set for this channel, so nothing can pass." })) : (_jsx(PolicyView, { policy: current }))] }), _jsx(CheckSection, { source: source, scopeId: scopeId }), _jsx(RecordsSection, { source: source, rev: rev, scopeId: scopeId })] }));
25
+ }
26
+ /** The current policy revision. Thresholds are written as decimals; the engine holds them in thousandths. */
27
+ function PolicyView({ policy }) {
28
+ return (_jsxs("dl", { className: "op-dl", children: [_jsx("dt", { children: "Revision" }), _jsx("dd", { children: groupDigits(String(policy.revision)) }), _jsx("dt", { children: "Created (UTC)" }), _jsx("dd", { children: instantLabel(policy.createdAtUtc, "UTC") }), _jsx("dt", { children: "Banned phrases" }), _jsx("dd", { children: listOrNone(policy.bannedPhrases) }), _jsx("dt", { children: "Stock openers" }), _jsx("dd", { children: listOrNone(policy.stockOpeners) }), _jsx("dt", { children: "Minimum words" }), _jsx("dd", { children: groupDigits(String(policy.minWords)) }), _jsx("dt", { children: "Dashes" }), _jsxs("dd", { children: ["At most ", formatMilli(policy.dashMaxPer100WordsMilli), " per 100 words"] }), _jsx("dt", { children: "Semicolons" }), _jsxs("dd", { children: ["At most ", formatMilli(policy.semicolonMaxPer100WordsMilli), " per 100 words"] }), _jsx("dt", { children: "Sentence length variance" }), _jsxs("dd", { children: ["At least ", formatMilli(policy.varianceMinMilli), ", inconclusive below", " ", groupDigits(String(policy.varianceMinSentences)), " sentences"] }), _jsx("dt", { children: "Tricolon sentences" }), _jsxs("dd", { children: ["At most ", formatMilli(policy.tricolonMaxPer100SentencesMilli), " per 100 sentences, inconclusive below", " ", groupDigits(String(policy.tricolonMinSentences)), " sentences"] }), _jsx("dt", { children: "Inconclusive or unverified blocks, for" }), _jsx("dd", { children: requiredRules(policy.required) })] }));
29
+ }
30
+ /** A dry-run check of one script. The subject id labels the check; the check stores nothing. */
31
+ function CheckSection({ source, scopeId }) {
32
+ const [subjectId, setSubjectId] = useState("");
33
+ const [text, setText] = useState("");
34
+ const [busy, setBusy] = useState(false);
35
+ const [result, setResult] = useState(null);
36
+ const [problem, setProblem] = useState(null);
37
+ async function runCheck(event) {
38
+ event.preventDefault();
39
+ if (subjectId.trim() === "") {
40
+ setResult(null);
41
+ setProblem("Type a subject id first. Nothing was checked.");
42
+ return;
43
+ }
44
+ if (text.trim() === "") {
45
+ setResult(null);
46
+ setProblem("Paste a script to check. Nothing was checked.");
47
+ return;
48
+ }
49
+ setBusy(true);
50
+ setProblem(null);
51
+ const outcome = await source.checkText(scopeId, subjectId.trim(), text);
52
+ setBusy(false);
53
+ if (outcome.ok) {
54
+ setResult(outcome.value);
55
+ }
56
+ else {
57
+ setResult(null);
58
+ setProblem(`The check did not run. ${outcome.error.message} Nothing was stored.`);
59
+ }
60
+ }
61
+ return (_jsxs("section", { "aria-labelledby": "gate-check-title", children: [_jsx("h2", { id: "gate-check-title", className: "op-section-title", children: "Check a script" }), _jsx("p", { className: "op-hint", children: "A script that contains a number will be blocked here, because this screen sends no claim annotations. That is the gate working, not an error." }), _jsxs("form", { className: "op-form", onSubmit: runCheck, children: [_jsx(Field, { label: "Subject id", note: "The brief, hook or script this text belongs to. It labels the check and stores nothing.", children: ({ id, "aria-describedby": describedBy }) => (_jsx(Input, { id: id, "aria-describedby": describedBy, type: "text", value: subjectId, onChange: (e) => setSubjectId(e.target.value) })) }), _jsx(Field, { label: "Script", children: ({ id, "aria-describedby": describedBy }) => (_jsx("textarea", { id: id, "aria-describedby": describedBy, className: "op-textarea", rows: 8, value: text, onChange: (e) => setText(e.target.value) })) }), _jsx("div", { className: "op-decide__buttons", children: _jsx(Button, { type: "submit", variant: "primary", disabled: busy, children: "Check" }) })] }), busy ? _jsx("p", { role: "status", className: "op-hint", children: "Checking the script" }) : null, problem !== null ? (_jsx("p", { role: "alert", className: "op-error-inline", children: problem })) : null, _jsx("div", { "aria-live": "polite", children: result !== null ? _jsx(CheckResult, { check: result }) : null })] }));
62
+ }
63
+ /** The result of one check: the overall disposition in words, then one row per rule with the engine's reason. */
64
+ function CheckResult({ check }) {
65
+ const disposition = GATE_DISPOSITION_LABEL[check.disposition];
66
+ return (_jsxs("article", { className: "op-detail", "aria-labelledby": "gate-result-title", children: [_jsxs("h3", { id: "gate-result-title", className: "op-detail__title", children: ["Result for ", check.subjectId] }), _jsx("div", { className: "op-badges", children: _jsx(Badge, { tone: disposition.tone, children: disposition.text }) }), _jsxs("dl", { className: "op-dl", children: [_jsx("dt", { children: "Policy revision" }), _jsx("dd", { children: groupDigits(String(check.policyRevision)) }), _jsx("dt", { children: "Blocking rules" }), _jsx("dd", { children: listOrNone(check.blockingRules) }), _jsx("dt", { children: "Text hash" }), _jsx("dd", { className: "op-mono", children: check.textHash }), _jsx("dt", { children: "Stored" }), _jsx("dd", { children: "Nothing. A check from this screen does not record." })] }), _jsx("div", { className: "op-table-scroll", children: _jsxs(Table, { children: [_jsx(TableCaption, { children: "Rules checked on this script" }), _jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableHeaderCell, { children: "Rule" }), _jsx(TableHeaderCell, { children: "Verdict" }), _jsx(TableHeaderCell, { children: "Blocks" }), _jsx(TableHeaderCell, { children: "Reason" })] }) }), _jsx(TableBody, { children: check.rules.map((rule) => (_jsxs(TableRow, { children: [_jsx(TableCell, { rowHeader: true, className: "op-mono", children: rule.ruleId }), _jsx(TableCell, { children: GATE_VERDICT_LABEL[rule.verdict] }), _jsx(TableCell, { children: rule.blocks ? "Yes" : "No" }), _jsx(TableCell, { children: rule.reason })] }, rule.ruleId))) })] }) })] }));
67
+ }
68
+ /** The stored verdict records of one subject. Only recorded checks are stored, so a check from this screen never appears. */
69
+ function RecordsSection({ source, rev, scopeId }) {
70
+ const [draft, setDraft] = useState("");
71
+ const [subjectId, setSubjectId] = useState(null);
72
+ function showRecords(event) {
73
+ event.preventDefault();
74
+ const id = draft.trim();
75
+ if (id !== "")
76
+ setSubjectId(id);
77
+ }
78
+ return (_jsxs("section", { "aria-labelledby": "gate-records-title", children: [_jsx("h2", { id: "gate-records-title", className: "op-section-title", children: "Stored verdict records" }), _jsx("p", { className: "op-hint", children: "Only checks the daemon recorded are stored. A check from this screen is never stored, so it never appears here." }), _jsxs("form", { className: "op-form", onSubmit: showRecords, children: [_jsx(Field, { label: "Subject id to read", children: ({ id, "aria-describedby": describedBy }) => (_jsx(Input, { id: id, "aria-describedby": describedBy, type: "text", value: draft, onChange: (e) => setDraft(e.target.value) })) }), _jsx("div", { className: "op-decide__buttons", children: _jsx(Button, { type: "submit", variant: "secondary", disabled: draft.trim() === "", children: "Show records" }) })] }), subjectId === null ? null : (_jsx(GateRecordsTable, { source: source, rev: rev, scopeId: scopeId, subjectId: subjectId }, subjectId))] }));
79
+ }
80
+ function GateRecordsTable({ source, rev, scopeId, subjectId, }) {
81
+ const { state, reload, value } = useAsync(() => source.gateRecords(scopeId, subjectId), [source, rev, scopeId, subjectId]);
82
+ if (state.status === "error") {
83
+ return _jsx(LoadError, { message: "Could not read the stored records from the daemon.", onRetry: reload });
84
+ }
85
+ if (value === null)
86
+ return _jsx(SkeletonRows, { label: "Loading the stored records", rows: 2 });
87
+ if (value.length === 0) {
88
+ return (_jsx(EmptyState, { title: "No stored records for this subject", children: _jsxs("p", { children: ["Nothing has been recorded for ", subjectId, " in this channel. This is not an error."] }) }));
89
+ }
90
+ return (_jsx("div", { className: "op-table-scroll", children: _jsxs(Table, { children: [_jsxs(TableCaption, { children: ["Stored verdict records of ", subjectId, ", newest first"] }), _jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableHeaderCell, { align: "end", children: "Record" }), _jsx(TableHeaderCell, { children: "Disposition" }), _jsx(TableHeaderCell, { align: "end", children: "Policy revision" }), _jsx(TableHeaderCell, { children: "Checked (UTC)" }), _jsx(TableHeaderCell, { children: "Text hash" })] }) }), _jsx(TableBody, { children: value.map((record) => (_jsxs(TableRow, { children: [_jsx(TableCell, { rowHeader: true, numeric: true, children: groupDigits(String(record.recordSeq)) }), _jsx(TableCell, { children: GATE_DISPOSITION_LABEL[record.disposition].text }), _jsx(TableCell, { numeric: true, children: groupDigits(String(record.policyRevision)) }), _jsx(TableCell, { className: "op-mono", children: instantLabel(record.checkedAtUtc, "UTC") }), _jsx(TableCell, { className: "op-mono", children: record.textHash })] }, record.recordSeq))) })] }) }));
91
+ }
92
+ function listOrNone(items) {
93
+ return items.length === 0 ? "None" : items.join(", ");
94
+ }
95
+ /** The style rules whose inconclusive or unverified finding blocks. Named in words. */
96
+ function requiredRules(required) {
97
+ const names = [];
98
+ if (required.bannedPhrase)
99
+ names.push("banned phrase");
100
+ if (required.dashDensity)
101
+ names.push("dash density");
102
+ if (required.semicolonDensity)
103
+ names.push("semicolon density");
104
+ if (required.sentenceLengthVariance)
105
+ names.push("sentence length variance");
106
+ if (required.tricolonFrequency)
107
+ names.push("tricolon frequency");
108
+ if (required.stockOpener)
109
+ names.push("stock opener");
110
+ return listOrNone(names);
111
+ }
@@ -0,0 +1,8 @@
1
+ import { type DataSource } from "../data/index.js";
2
+ interface Props {
3
+ source: DataSource;
4
+ rev: number;
5
+ onChanged: () => void;
6
+ }
7
+ export declare function ThreadsScreen({ source, rev, onChanged }: Props): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,113 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Badge, Button, Notice, PageShell, SegmentedControl, SkeletonRows } from "@rightkit/react-shell/react";
4
+ import { AccountChip, LoadError, MockBanner } from "../components/Shared.js";
5
+ import { useAsync } from "../hooks/useAsync.js";
6
+ import { accountOrdinals, instantLabel, } from "../data/index.js";
7
+ import { PLATFORM_LABEL } from "./labels.js";
8
+ const ACTION_LABEL = {
9
+ hide_comment: "Hide comment",
10
+ delete_comment: "Delete comment",
11
+ block_author: "Block author",
12
+ };
13
+ const SNOOZE_HOURS = 4;
14
+ const SNOOZE_DAY_HOURS = 24;
15
+ function threadStatusText(thread, tz) {
16
+ if (thread.status === "resolved")
17
+ return { text: "Resolved", tone: "ok" };
18
+ if (thread.status === "snoozed" && thread.snoozedUntilUtc) {
19
+ return { text: `Snoozed until ${instantLabel(thread.snoozedUntilUtc, tz)}`, tone: "warn" };
20
+ }
21
+ return { text: "Open", tone: "neutral" };
22
+ }
23
+ function autoReplyBadge(thread) {
24
+ if (!thread.autoReply)
25
+ return null;
26
+ return thread.autoReply.state === "matched_not_sent"
27
+ ? { text: "Would reply, not sent", tone: "accent" }
28
+ : { text: "Auto-reply unavailable", tone: "bad" };
29
+ }
30
+ export function ThreadsScreen({ source, rev, onChanged }) {
31
+ const { state, reload, value } = useAsync(async () => {
32
+ const [accounts, threads, capabilities] = await Promise.all([
33
+ source.listAccounts(),
34
+ source.listThreads(),
35
+ source.listModerationCapabilities(),
36
+ ]);
37
+ return { accounts, threads, capabilities };
38
+ }, [source, rev]);
39
+ const [filter, setFilter] = useState("open");
40
+ const [selectedId, setSelectedId] = useState(null);
41
+ const accounts = useMemo(() => value?.accounts ?? [], [value]);
42
+ const ordinals = useMemo(() => accountOrdinals(accounts), [accounts]);
43
+ const all = value?.threads ?? [];
44
+ const counts = {
45
+ open: all.filter((t) => t.status === "open").length,
46
+ snoozed: all.filter((t) => t.status === "snoozed").length,
47
+ resolved: all.filter((t) => t.status === "resolved").length,
48
+ };
49
+ const visible = all.filter((t) => t.status === filter);
50
+ const groups = accounts
51
+ .map((account) => {
52
+ const mine = visible.filter((t) => t.accountId === account.id);
53
+ const posts = new Map();
54
+ for (const t of mine) {
55
+ const key = `${t.postTitle}|${t.postPublishedUtc}`;
56
+ const post = posts.get(key) ?? { title: t.postTitle, publishedUtc: t.postPublishedUtc, threads: [] };
57
+ post.threads.push(t);
58
+ posts.set(key, post);
59
+ }
60
+ return { account, posts: [...posts.values()] };
61
+ })
62
+ .filter((g) => g.posts.length > 0);
63
+ const selected = selectedId ? all.find((t) => t.id === selectedId) ?? null : null;
64
+ const selectedAccount = selected ? accounts.find((a) => a.id === selected.accountId) ?? null : null;
65
+ return (_jsxs(PageShell, { title: "Threads", subtitle: "Comments and messages on published posts. Read, resolve and snooze here. Replies are not sent from this screen.", children: [source.kind === "mock" ? _jsx(MockBanner, {}) : null, _jsx("div", { className: "op-inbox__filters", children: _jsx(SegmentedControl, { label: "Show", value: filter, onChange: (f) => {
66
+ setFilter(f);
67
+ setSelectedId(null);
68
+ }, options: [
69
+ { value: "open", label: `Open (${counts.open})` },
70
+ { value: "snoozed", label: `Snoozed (${counts.snoozed})` },
71
+ { value: "resolved", label: `Resolved (${counts.resolved})` },
72
+ ] }) }), state.status === "error" ? (_jsx(LoadError, { message: "Could not read threads from the daemon.", onRetry: reload })) : value === null ? (_jsx(SkeletonRows, { label: "Loading threads", rows: 4 })) : (_jsxs("div", { className: "op-inbox", children: [_jsxs("section", { className: "op-inbox__list", "aria-labelledby": "threads-list-title", children: [_jsx("h2", { id: "threads-list-title", className: "op-section-title", children: "Threads" }), groups.length === 0 ? _jsx("p", { className: "op-hint", children: "No threads in this view." }) : null, groups.map((group) => (_jsxs("section", { className: "op-inbox__group", "aria-label": group.account.handle, children: [_jsx("div", { className: "op-inbox__group-head", children: _jsx(AccountChip, { account: group.account, ordinal: ordinals.get(group.account.id) }) }), group.posts.map((post) => (_jsxs("div", { className: "op-thread-post", children: [_jsxs("h3", { className: "op-thread-post__title", children: [post.title, ", published ", instantLabel(post.publishedUtc, group.account.timeZone)] }), _jsx("ul", { children: post.threads.map((thread) => {
73
+ const latest = thread.messages[thread.messages.length - 1];
74
+ const badge = autoReplyBadge(thread);
75
+ return (_jsx("li", { children: _jsxs(Button, { type: "button", variant: "secondary", className: "op-row", "aria-current": thread.id === selectedId ? "true" : undefined, onClick: () => setSelectedId(thread.id), children: [_jsxs("span", { className: "op-row__time", children: [thread.kind === "dm" ? "Direct message" : "Comment", " from ", latest?.author ?? "unknown"] }), _jsx("span", { className: "op-row__meta", children: latest?.text ?? "" }), badge ? _jsx(Badge, { tone: badge.tone, children: badge.text }) : null] }) }, thread.id));
76
+ }) })] }, `${post.title}|${post.publishedUtc}`)))] }, group.account.id)))] }), _jsx("section", { className: "op-inbox__preview", "aria-label": "Thread detail", children: selected && selectedAccount ? (_jsx(ThreadDetail, { thread: selected, account: selectedAccount, ordinal: ordinals.get(selectedAccount.id), capabilities: value.capabilities.filter((c) => c.platform === selectedAccount.platform), nowUtc: source.nowUtc(), source: source, onChanged: onChanged }, selected.id)) : (_jsx("p", { className: "op-hint", children: "Select a thread to read it and see what an automated reply would do." })) })] }))] }));
77
+ }
78
+ function ThreadDetail({ thread, account, ordinal, capabilities, nowUtc, source, onChanged, }) {
79
+ const [busy, setBusy] = useState(false);
80
+ const [notice, setNotice] = useState(null);
81
+ const tz = account.timeZone;
82
+ const status = threadStatusText(thread, tz);
83
+ const closesMs = thread.replyWindow ? Date.parse(thread.replyWindow.closesUtc) : null;
84
+ const windowOpen = closesMs !== null && closesMs > Date.parse(nowUtc);
85
+ const kindLabel = thread.kind === "dm" ? "Direct message" : "Comment";
86
+ const resolved = thread.status === "resolved";
87
+ async function resolveThread() {
88
+ setBusy(true);
89
+ const result = await source.resolveThread(thread.id);
90
+ setBusy(false);
91
+ if (result.ok) {
92
+ setNotice({ tone: "ok", text: "Thread resolved." });
93
+ onChanged();
94
+ }
95
+ else {
96
+ setNotice({ tone: "bad", text: result.error.message });
97
+ }
98
+ }
99
+ async function snooze(hours) {
100
+ setBusy(true);
101
+ const until = new Date(Date.parse(nowUtc) + hours * 3_600_000).toISOString();
102
+ const result = await source.snoozeThread(thread.id, until);
103
+ setBusy(false);
104
+ if (result.ok) {
105
+ setNotice({ tone: "ok", text: `Snoozed until ${instantLabel(until, tz)}.` });
106
+ onChanged();
107
+ }
108
+ else {
109
+ setNotice({ tone: "bad", text: result.error.message });
110
+ }
111
+ }
112
+ return (_jsxs("article", { className: `op-preview op-band-${account.band}`, "aria-labelledby": "thread-title", children: [_jsxs("header", { className: "op-preview__account", children: [_jsxs("p", { className: "op-kicker", children: [kindLabel, " on ", thread.postTitle] }), _jsxs("h2", { id: "thread-title", className: "op-preview__title", children: [PLATFORM_LABEL[account.platform], ": ", account.displayName, " ", account.handle] }), _jsx(AccountChip, { account: account, ordinal: ordinal, detail: true })] }), _jsxs("section", { "aria-labelledby": "th-status", children: [_jsx("h3", { id: "th-status", className: "op-section-title", children: "Status" }), _jsx("p", { children: status.text })] }), _jsxs("section", { "aria-labelledby": "th-window", children: [_jsx("h3", { id: "th-window", className: "op-section-title", children: "Reply window" }), closesMs === null ? (_jsx("p", { children: "No platform reply window applies to this kind of thread." })) : windowOpen ? (_jsxs("p", { children: ["Reply window closes ", instantLabel(thread.replyWindow.closesUtc, tz), "."] })) : (_jsxs("p", { children: ["Reply window closed on ", instantLabel(thread.replyWindow.closesUtc, tz), ". Replies are not possible now."] })), _jsx(Button, { variant: "secondary", size: "sm", disabled: true, children: "Reply" }), _jsx("p", { className: "op-hint", children: "Replying is not part of this screen yet. Replies are drafted on the desktop in a later step." })] }), _jsxs("section", { "aria-labelledby": "th-messages", children: [_jsx("h3", { id: "th-messages", className: "op-section-title", children: "Messages" }), _jsx("ol", { className: "op-messages", children: thread.messages.map((m) => (_jsxs("li", { children: [_jsx("strong", { children: m.fromOperator ? "You" : m.author }), " ", _jsx("time", { dateTime: m.sentAtUtc, children: instantLabel(m.sentAtUtc, tz) }), _jsx("p", { className: "op-caption", children: m.text })] }, m.id))) })] }), _jsxs("section", { "aria-labelledby": "th-auto", children: [_jsx("h3", { id: "th-auto", className: "op-section-title", children: "Automated reply" }), thread.autoReply === null ? (_jsx("p", { children: "No automated reply rule matches this thread." })) : thread.autoReply.state === "matched_not_sent" ? (_jsxs(_Fragment, { children: [_jsxs("p", { children: ["Rule ", _jsx("strong", { children: thread.autoReply.ruleName }), " matches the keyword", " ", _jsx("code", { children: thread.autoReply.matchedKeyword }), ". Not sent: this is a preview only."] }), _jsx("p", { className: "op-caption", children: thread.autoReply.draftReply })] })) : (_jsx(_Fragment, { children: _jsxs("p", { role: "status", className: "op-invalid", children: ["Automated reply unavailable for rule ", _jsx("strong", { children: thread.autoReply.ruleName }), ".", " ", thread.autoReply.unavailableReason] }) }))] }), _jsxs("section", { "aria-labelledby": "th-moderation", children: [_jsxs("h3", { id: "th-moderation", className: "op-section-title", children: ["Moderation on ", PLATFORM_LABEL[account.platform]] }), capabilities.length === 0 ? (_jsxs("p", { className: "op-hint", children: ["No moderation capability is listed for ", PLATFORM_LABEL[account.platform], " in this app."] })) : null, _jsx("ul", { className: "op-moderation", children: capabilities.map((c) => (_jsxs("li", { children: [_jsxs("strong", { children: [ACTION_LABEL[c.action], ":"] }), " ", c.supported ? "Supported." : "Unavailable.", " ", c.reason] }, c.action))) }), _jsx("p", { className: "op-hint", children: "Mock capability list. Each row is confirmed per platform before live use." })] }), _jsxs("section", { className: "op-decide", "aria-labelledby": "th-decide", children: [_jsx("h3", { id: "th-decide", className: "op-section-title", children: "Decision" }), _jsxs("div", { className: "op-decide__buttons", children: [_jsx(Button, { variant: "primary", disabled: busy || resolved, onClick: resolveThread, children: "Resolve thread" }), _jsxs(Button, { variant: "secondary", disabled: busy || resolved, onClick: () => snooze(SNOOZE_HOURS), children: ["Snooze ", SNOOZE_HOURS, " hours"] }), _jsx(Button, { variant: "secondary", disabled: busy || resolved, onClick: () => snooze(SNOOZE_DAY_HOURS), children: "Snooze 24 hours" })] }), _jsx("div", { "aria-live": "polite", children: notice ? (_jsx(Notice, { tone: notice.tone, className: "op-notice", children: notice.text })) : null })] })] }));
113
+ }
@@ -0,0 +1,7 @@
1
+ import { type DataSource } from "../data/index.js";
2
+ interface Props {
3
+ source: DataSource;
4
+ rev: number;
5
+ }
6
+ export declare function TypedBriefsScreen({ source, rev }: Props): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,75 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { EmptyState, Field, PageShell, SkeletonRows, Table, TableBody, TableCaption, TableCell, TableHead, TableHeaderCell, TableRow } from "@rightkit/react-shell/react";
4
+ import { ScopeSelect } from "../components/ScopeSelect.js";
5
+ import { LoadError, MockBanner } from "../components/Shared.js";
6
+ import { useAsync } from "../hooks/useAsync.js";
7
+ import { groupDigits, instantLabel, } from "../data/index.js";
8
+ import { BRIEF_TYPE_LABEL, LINK_PLACEMENT_LABEL, platformName } from "./labels.js";
9
+ /** The explainer's five beats in story order. Each key is the beat's own key on ExplainerBeats. */
10
+ const EXPLAINER_BEATS = [
11
+ { key: "question", title: "Question" },
12
+ { key: "model", title: "Model" },
13
+ { key: "proof", title: "Proof" },
14
+ { key: "turn", title: "Turn" },
15
+ { key: "payoff", title: "Payoff" },
16
+ ];
17
+ export function TypedBriefsScreen({ source, rev }) {
18
+ const scopes = useAsync(() => source.listScopes(), [source, rev]);
19
+ const [picked, setPicked] = useState(null);
20
+ const scopeList = scopes.value ?? [];
21
+ const scopeId = scopeList.find((s) => s.id === picked)?.id ?? scopeList[0]?.id ?? null;
22
+ return (_jsxs(PageShell, { title: "Typed briefs", subtitle: "Read only. The typed briefs of one channel, the platform variants made from each brief. Nothing here writes a brief or derives a variant.", children: [source.kind === "mock" ? _jsx(MockBanner, {}) : null, scopes.state.status === "error" ? (_jsx(LoadError, { message: "Could not read the channels from the daemon.", onRetry: scopes.reload })) : scopes.value === null ? (_jsx(SkeletonRows, { label: "Loading channels", rows: 2 })) : scopeList.length === 0 || scopeId === null ? (_jsx(EmptyState, { title: "No channels yet", children: _jsx("p", { children: "Typed briefs are kept per channel once one is set up." }) })) : (_jsxs(_Fragment, { children: [_jsx(ScopeSelect, { scopes: scopeList, value: scopeId, onChange: (id) => setPicked(id) }), _jsx(BriefsView, { source: source, rev: rev, scopeId: scopeId }, scopeId)] }))] }));
23
+ }
24
+ function BriefsView({ source, rev, scopeId }) {
25
+ const { state, reload, value } = useAsync(() => source.listTypedBriefs(scopeId), [source, rev, scopeId]);
26
+ const [picked, setPicked] = useState(null);
27
+ if (state.status === "error") {
28
+ return _jsx(LoadError, { message: "Could not read the typed briefs from the daemon.", onRetry: reload });
29
+ }
30
+ if (value === null)
31
+ return _jsx(SkeletonRows, { label: "Loading typed briefs", rows: 3 });
32
+ if (value.length === 0) {
33
+ return (_jsx(EmptyState, { title: "No typed briefs yet", children: _jsx("p", { children: "This channel has no typed brief. A brief is written on the daemon host or with the CLI, not here. This is not an error." }) }));
34
+ }
35
+ const selected = value.find((b) => b.briefId === picked) ?? value[0];
36
+ return (_jsxs(_Fragment, { children: [_jsxs("section", { "aria-labelledby": "typed-list-title", children: [_jsxs("h2", { id: "typed-list-title", className: "op-section-title", children: ["Typed briefs (", groupDigits(String(value.length)), ")"] }), _jsx("div", { className: "op-table-scroll", children: _jsxs(Table, { "aria-labelledby": "typed-list-title", children: [_jsx(TableCaption, { children: "Typed briefs in this channel, by brief id" }), _jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableHeaderCell, { children: "Brief id" }), _jsx(TableHeaderCell, { children: "Type" }), _jsx(TableHeaderCell, { children: "Hook" }), _jsx(TableHeaderCell, { align: "end", children: "Revision" }), _jsx(TableHeaderCell, { children: "Last changed (UTC)" })] }) }), _jsx(TableBody, { children: value.map((brief) => (_jsxs(TableRow, { children: [_jsx(TableCell, { rowHeader: true, className: "op-mono", children: brief.briefId }), _jsx(TableCell, { children: BRIEF_TYPE_LABEL[brief.briefType] }), _jsx(TableCell, { children: brief.hook }), _jsx(TableCell, { numeric: true, children: groupDigits(String(brief.revision)) }), _jsx(TableCell, { className: "op-mono", children: instantLabel(brief.updatedAtUtc, "UTC") })] }, brief.briefId))) })] }) })] }), _jsxs("section", { "aria-labelledby": "typed-detail-heading", children: [_jsx("h2", { id: "typed-detail-heading", className: "op-section-title", children: "Selected typed brief" }), _jsx(Field, { label: "Typed brief", children: ({ id, "aria-describedby": describedBy }) => (_jsx("select", { id: id, "aria-describedby": describedBy, className: "op-select", value: selected.briefId, onChange: (e) => setPicked(e.target.value), children: value.map((brief) => (_jsxs("option", { value: brief.briefId, children: [brief.briefId, " (", BRIEF_TYPE_LABEL[brief.briefType], ")"] }, brief.briefId))) })) }), _jsx(TypedBriefDetail, { source: source, rev: rev, scopeId: scopeId, briefId: selected.briefId }, selected.briefId)] })] }));
37
+ }
38
+ function TypedBriefDetail({ source, rev, scopeId, briefId, }) {
39
+ const brief = useAsync(() => source.getTypedBrief(scopeId, briefId), [source, rev, scopeId, briefId]);
40
+ const variants = useAsync(() => source.listBriefVariants(scopeId, briefId), [source, rev, scopeId, briefId]);
41
+ if (brief.state.status === "error") {
42
+ return _jsx(LoadError, { message: "Could not read this typed brief from the daemon.", onRetry: brief.reload });
43
+ }
44
+ if (variants.state.status === "error") {
45
+ return _jsx(LoadError, { message: "Could not read the platform variants from the daemon.", onRetry: variants.reload });
46
+ }
47
+ if (brief.state.status === "loading" || variants.state.status === "loading") {
48
+ return _jsx(SkeletonRows, { label: "Loading the typed brief", rows: 3 });
49
+ }
50
+ const record = brief.state.value;
51
+ if (record === null)
52
+ return _jsx("p", { className: "op-hint", children: "This typed brief is no longer in this channel." });
53
+ const derived = variants.state.value;
54
+ return (_jsxs("article", { className: "op-detail", "aria-labelledby": "typed-detail-title", children: [_jsx("h3", { id: "typed-detail-title", className: "op-detail__title", children: record.briefId }), _jsxs("dl", { className: "op-dl", children: [_jsx("dt", { children: "Brief id" }), _jsx("dd", { className: "op-mono", children: record.briefId }), _jsx("dt", { children: "Type" }), _jsx("dd", { children: BRIEF_TYPE_LABEL[record.briefType] }), _jsx("dt", { children: "Revision" }), _jsx("dd", { children: groupDigits(String(record.revision)) }), _jsx("dt", { children: "Created (UTC)" }), _jsx("dd", { children: instantLabel(record.createdAtUtc, "UTC") }), _jsx("dt", { children: "Last changed (UTC)" }), _jsx("dd", { children: instantLabel(record.updatedAtUtc, "UTC") })] }), _jsx("h4", { className: "op-section-title", children: "Hook" }), _jsx("div", { className: "op-caption", children: record.hook }), _jsx("h4", { className: "op-section-title", children: "Body" }), _jsx("div", { className: "op-caption", children: record.body }), record.briefType === "explainer" ? _jsx(ExplainerParts, { beats: record.explainer }) : null, record.briefType === "product_demo" ? _jsx(DemoSteps, { steps: record.demoSteps }) : null, _jsx(VariantSection, { variants: derived, currentRevision: record.revision })] }));
55
+ }
56
+ /** The explainer's five beats, each with its scene spec. A beat the engine lacks is never invented. */
57
+ function ExplainerParts({ beats }) {
58
+ if (beats === null) {
59
+ return (_jsxs("section", { "aria-labelledby": "typed-beats-title", children: [_jsx("h4", { id: "typed-beats-title", className: "op-section-title", children: "Explainer beats" }), _jsx("p", { className: "op-hint", children: "The engine holds no beats for this explainer." })] }));
60
+ }
61
+ const scenes = beats;
62
+ return (_jsxs("section", { "aria-labelledby": "typed-beats-title", children: [_jsx("h4", { id: "typed-beats-title", className: "op-section-title", children: "Explainer beats" }), _jsx("ol", { className: "op-id-list", children: EXPLAINER_BEATS.map((beat, index) => (_jsxs("li", { children: [_jsx("p", { children: _jsxs("strong", { children: ["Beat ", groupDigits(String(index + 1)), ": ", beat.title] }) }), _jsx(SceneSpecView, { scene: scenes[beat.key] })] }, beat.key))) })] }));
63
+ }
64
+ function SceneSpecView({ scene }) {
65
+ return (_jsxs("dl", { className: "op-dl", children: [_jsx("dt", { children: "Teaches" }), _jsx("dd", { children: scene.teaches }), _jsx("dt", { children: "Frame" }), _jsx("dd", { children: scene.frame }), _jsx("dt", { children: "Motion" }), _jsx("dd", { children: scene.motion }), _jsx("dt", { children: "Words" }), _jsx("dd", { children: scene.words }), _jsx("dt", { children: "Sound" }), _jsx("dd", { children: scene.sound })] }));
66
+ }
67
+ /** The product demo's steps, in the order they are recorded. Each step is the text said during it. */
68
+ function DemoSteps({ steps }) {
69
+ return (_jsxs("section", { "aria-labelledby": "typed-steps-title", children: [_jsx("h4", { id: "typed-steps-title", className: "op-section-title", children: "Demo steps" }), steps.length === 0 ? (_jsx("p", { className: "op-hint", children: "The engine holds no demo steps for this brief." })) : (_jsx("ol", { className: "op-id-list", children: steps.map((step, index) => (_jsx("li", { children: step.say }, `step-${index}`))) }))] }));
70
+ }
71
+ /** The platform variants of one brief, with the revision and rule revision each was made under. */
72
+ function VariantSection({ variants, currentRevision }) {
73
+ const older = variants.filter((variant) => variant.briefRevision < currentRevision).length;
74
+ return (_jsxs("section", { "aria-labelledby": "typed-variants-title", children: [_jsx("h4", { id: "typed-variants-title", className: "op-section-title", children: "Platform variants" }), _jsx("p", { className: "op-hint", children: "A variant is one platform rendering of this brief, made under one platform rule revision. The engine keeps each variant as it was made: an edit to the brief or a new rule revision makes a new variant rather than changing this one." }), _jsx("p", { className: "op-hint", children: "The engine stores no separate hashtag list, and keeps no duration on a variant. Hashtags appear in the caption text as stored. The duration is checked against the rule when a variant is made, and is not shown here." }), older > 0 ? (_jsxs("p", { className: "op-hint", children: [groupDigits(String(older)), " ", older === 1 ? "variant was" : "variants were", " made from an older revision of this brief. The brief is now at revision ", groupDigits(String(currentRevision)), "."] })) : null, variants.length === 0 ? (_jsx("p", { className: "op-hint", children: "No platform variant is stored for this brief yet." })) : (_jsx("div", { className: "op-table-scroll", children: _jsxs(Table, { "aria-labelledby": "typed-variants-title", children: [_jsx(TableCaption, { children: "Platform variants of this brief, by platform" }), _jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableHeaderCell, { children: "Variant id" }), _jsx(TableHeaderCell, { children: "Platform" }), _jsx(TableHeaderCell, { children: "Caption, as stored" }), _jsx(TableHeaderCell, { children: "Link placement" }), _jsx(TableHeaderCell, { children: "Link" }), _jsx(TableHeaderCell, { align: "end", children: "Brief revision" }), _jsx(TableHeaderCell, { align: "end", children: "Rule revision" }), _jsx(TableHeaderCell, { children: "Created (UTC)" })] }) }), _jsx(TableBody, { children: variants.map((variant) => (_jsxs(TableRow, { children: [_jsx(TableCell, { rowHeader: true, className: "op-mono", children: variant.variantId }), _jsx(TableCell, { children: platformName(variant.platform) }), _jsx(TableCell, { children: _jsx("pre", { className: "op-pre", tabIndex: 0, "aria-label": `Caption of the ${platformName(variant.platform)} variant ${variant.variantId} as stored`, children: variant.caption }) }), _jsx(TableCell, { children: LINK_PLACEMENT_LABEL[variant.linkPlacement] }), _jsx(TableCell, { children: variant.link === null ? "No link given" : _jsx("span", { className: "op-mono", children: variant.link }) }), _jsx(TableCell, { numeric: true, children: groupDigits(String(variant.briefRevision)) }), _jsx(TableCell, { numeric: true, children: variant.ruleRevision }), _jsx(TableCell, { className: "op-mono", children: instantLabel(variant.createdAtUtc, "UTC") })] }, variant.variantId))) })] }) }))] }));
75
+ }
@@ -0,0 +1,121 @@
1
+ import type { AnalyticsSource, Approval, BriefState, BriefType, DispatchOutcome, GateDisposition, GateVerdict, ContentMediaKind, ContentState, ContentVariantKind, ContentVerdict, Entitlement, HeartbeatState, InboxKind, InboxState, PacingReason, HookTestRole, HookTestState, JobFormat, JobState, LinkPlacement, OptimiseState, ProofStatus, PublishProgressState, PublishResolutionDecision, RejectReason, SocialPlatform, TokenHealth } from "../data/index.js";
2
+ /** The ordinary name of each account platform. Exhaustive over SocialPlatform: a new id fails to compile until it is named. */
3
+ export declare const PLATFORM_LABEL: Record<SocialPlatform, string>;
4
+ /**
5
+ * The display name for any platform id a stored row carries. A known id gets its name. An id the desktop does
6
+ * not know (a newer engine) is shown with its raw id and the words "unknown platform", never dropped.
7
+ */
8
+ export declare function platformName(platform: string): string;
9
+ export declare const FORMAT_LABEL: Record<JobFormat, string>;
10
+ /** Suggestion states. Text carries the meaning; the tone is a secondary cue. */
11
+ export declare const OPTIMISE_STATE_LABEL: Record<OptimiseState, {
12
+ text: string;
13
+ tone: Tone;
14
+ }>;
15
+ export type Tone = "neutral" | "accent" | "ok" | "warn" | "bad";
16
+ /** Hook test states. An open set is read on the daemon host, so the label says so. */
17
+ export declare const HOOK_STATE_LABEL: Record<HookTestState, {
18
+ text: string;
19
+ tone: Tone;
20
+ }>;
21
+ /** Opening roles in a decided hook test. Null (not decided) is written by the screen. */
22
+ export declare const HOOK_ROLE_LABEL: Record<HookTestRole, string>;
23
+ export declare const STATE_LABEL: Record<JobState, {
24
+ text: string;
25
+ tone: Tone;
26
+ }>;
27
+ /** The daemon's publish job state, shown beside the calendar state. Text carries the meaning. */
28
+ export declare const PUBLISH_STATE_LABEL: Record<PublishProgressState, {
29
+ text: string;
30
+ tone: Tone;
31
+ }>;
32
+ /** A person's recorded decision on an outcome_unknown job, in words. */
33
+ export declare const PUBLISH_RESOLUTION_LABEL: Record<PublishResolutionDecision, string>;
34
+ /** The plain explanation shown on every outcome_unknown failure. The wording is fixed. */
35
+ export declare const OUTCOME_UNKNOWN_EXPLANATION: string;
36
+ export declare const REJECT_LABEL: Record<RejectReason, string>;
37
+ export declare const MEDIA_STATUS_LABEL: {
38
+ readonly ready: "Ready";
39
+ readonly uploading: "Uploading";
40
+ readonly missing: "Missing";
41
+ };
42
+ export declare function approvalText(approval: Approval): {
43
+ text: string;
44
+ tone: Tone;
45
+ };
46
+ export declare function tokenText(token: TokenHealth, nowUtc: string): {
47
+ text: string;
48
+ tone: Tone;
49
+ mark: string;
50
+ };
51
+ export declare const ENTITLEMENT_LABEL: Record<Entitlement, {
52
+ text: string;
53
+ hint: string;
54
+ }>;
55
+ export declare function cooldownText(until: string | null, nowUtc: string): string;
56
+ export declare function countText(n: number): string;
57
+ /** Content record states. Withdrawn is final; expired is derived from the clock and never stored. */
58
+ export declare const CONTENT_STATE_LABEL: Record<ContentState, {
59
+ text: string;
60
+ tone: Tone;
61
+ }>;
62
+ export declare const CONTENT_MEDIA_KIND_LABEL: Record<ContentMediaKind, string>;
63
+ export declare const CONTENT_VARIANT_KIND_LABEL: Record<ContentVariantKind, string>;
64
+ /** The engine's experiment verdict in words. Neither verdict names a winner. */
65
+ export declare const CONTENT_VERDICT_LABEL: Record<ContentVerdict, {
66
+ text: string;
67
+ tone: Tone;
68
+ }>;
69
+ /** The text gate's overall result. The words say what happens next; the tone is a secondary cue only. */
70
+ export declare const GATE_DISPOSITION_LABEL: Record<GateDisposition, {
71
+ text: string;
72
+ tone: Tone;
73
+ }>;
74
+ /** One rule's verdict in words. */
75
+ export declare const GATE_VERDICT_LABEL: Record<GateVerdict, string>;
76
+ /** Outgoing brief states. An issued brief has no delivery yet; a partial one has only some of what was requested. */
77
+ export declare const BRIEF_STATE_LABEL: Record<BriefState, {
78
+ text: string;
79
+ tone: Tone;
80
+ }>;
81
+ /** Dispatch attempt outcomes. Each names what happened to the run, in words. */
82
+ export declare const DISPATCH_OUTCOME_LABEL: Record<DispatchOutcome, {
83
+ text: string;
84
+ tone: Tone;
85
+ }>;
86
+ /** Heartbeat states in words. "Stale" and "unreadable" are never shown as alive. */
87
+ export declare const HEARTBEAT_STATE_LABEL: Record<HeartbeatState, {
88
+ text: string;
89
+ tone: Tone;
90
+ }>;
91
+ /** Proof statuses in words. Only an approved proof can be cited by a script; the tone is a secondary cue only. */
92
+ export declare const PROOF_STATUS_LABEL: Record<ProofStatus, {
93
+ text: string;
94
+ tone: Tone;
95
+ }>;
96
+ /** Typed brief types in words. The type is text; no colour is used. */
97
+ export declare const BRIEF_TYPE_LABEL: Record<BriefType, string>;
98
+ /** Where a platform places a link, in words. "none" means the platform carries no link. */
99
+ export declare const LINK_PLACEMENT_LABEL: Record<LinkPlacement, string>;
100
+ /** A stored metric the platform does not provide. It is never shown as zero. */
101
+ export declare const ANALYTICS_UNAVAILABLE = "Not available from this platform";
102
+ /** Where a stored number came from, in words. An API row names its pull elsewhere; a manual row names its import. */
103
+ export declare function analyticsSourceText(source: AnalyticsSource, importId: string | null): string;
104
+ /** A post's age at an observation: the exact seconds, then the same span in days, hours and minutes. */
105
+ export declare function postAgeText(seconds: number): string;
106
+ /** Inbox event kinds in words. The kind is text; no colour is used. */
107
+ export declare const INBOX_KIND_LABEL: Record<InboxKind, string>;
108
+ /** Inbox event states in words. The state is set by a person on the server; this screen only shows it. */
109
+ export declare const INBOX_STATE_LABEL: Record<InboxState, {
110
+ text: string;
111
+ tone: Tone;
112
+ }>;
113
+ /** Why a publish job may not run now, in words. Every reason the daemon can give has a line here. */
114
+ export declare const PACING_REASON_LABEL: Record<PacingReason, string>;
115
+ /** An inbox kind as stored. A kind the desktop does not show is named as such, never dropped. */
116
+ export declare function inboxKindText(kind: string): string;
117
+ /** An inbox state as stored. A state the desktop does not show is named as such. */
118
+ export declare function inboxStateText(state: string): {
119
+ text: string;
120
+ tone: Tone;
121
+ };