@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,138 @@
1
+ import { SCOPE_ALPHA } from "./fixtures-optimise.js";
2
+ /** Two typed briefs in the first channel: one explainer and one product demo. */
3
+ export const FIXTURE_TYPED_BRIEFS = [
4
+ {
5
+ // Explainer at revision 2. Its instagram variant from revision 1 is kept as it was made, so the screen says so.
6
+ scopeId: SCOPE_ALPHA,
7
+ briefId: "explainer-puddle-edges",
8
+ briefType: "explainer",
9
+ hook: "Fixture hook: puddles freeze at the edges first.",
10
+ body: "Fixture body: a short outdoor explainer about why a shallow puddle skins over at its rim before its middle.",
11
+ explainer: {
12
+ question: {
13
+ teaches: "The viewer asks why the rim freezes first.",
14
+ frame: "A shallow puddle on grey paving, wide shot.",
15
+ motion: "Slow push in on the rim.",
16
+ words: "Ever notice the edge goes first?",
17
+ sound: "Light wind and a single drip.",
18
+ },
19
+ model: {
20
+ teaches: "Thin water at the edge cools faster.",
21
+ frame: "A cross-section sketch of a puddle.",
22
+ motion: "Arrows drawn over the sketch.",
23
+ words: "The edge holds less water to keep warm.",
24
+ sound: "Soft pencil scratch.",
25
+ },
26
+ proof: {
27
+ teaches: "A probe reads colder at the rim.",
28
+ frame: "Two probes, one at the rim and one at the middle.",
29
+ motion: "Static, then a cut to the readings.",
30
+ words: "The rim reads colder by the same morning.",
31
+ sound: "Clicks from a timer.",
32
+ },
33
+ turn: {
34
+ teaches: "Freezing starts where heat leaves fastest.",
35
+ frame: "The sketch again, with the rim shaded.",
36
+ motion: "The shading fades in.",
37
+ words: "So the rim is where the ice starts.",
38
+ sound: "Quiet ice cracking.",
39
+ },
40
+ payoff: {
41
+ teaches: "Next time, check the rim first.",
42
+ frame: "The puddle with a thin skin at its edge.",
43
+ motion: "Hold, then fade.",
44
+ words: "Watch the edge before you step on it.",
45
+ sound: "Wind again.",
46
+ },
47
+ },
48
+ demoSteps: [],
49
+ revision: 2,
50
+ createdAtUtc: "2026-09-20T09:00:00Z",
51
+ updatedAtUtc: "2026-10-05T10:00:00Z",
52
+ },
53
+ {
54
+ // Product demo at revision 1. Its steps are the text said while each step is recorded.
55
+ scopeId: SCOPE_ALPHA,
56
+ briefId: "demo-kettle-setup",
57
+ briefType: "product_demo",
58
+ hook: "Fixture hook: set up the fixture kettle in three steps.",
59
+ body: "Fixture body: a product demo of a fictional kettle, filmed on one bench.",
60
+ explainer: null,
61
+ demoSteps: [
62
+ { say: "Fill the fixture tank to the line." },
63
+ { say: "Lock the fixture lid until it clicks." },
64
+ { say: "Press the fixture button and wait for the light." },
65
+ ],
66
+ revision: 1,
67
+ createdAtUtc: "2026-10-03T09:00:00Z",
68
+ updatedAtUtc: "2026-10-03T09:00:00Z",
69
+ },
70
+ ];
71
+ /** Variants of the two briefs, on two platforms each. The explainer also has one instagram variant from revision 1. */
72
+ export const FIXTURE_BRIEF_VARIANTS = [
73
+ {
74
+ // Explainer, instagram, revision 1: an older variant, kept as it was made.
75
+ scopeId: SCOPE_ALPHA,
76
+ variantId: "var_3c1e0a9b7d5f4e21a6b08c9d7e5f4a32",
77
+ briefId: "explainer-puddle-edges",
78
+ briefRevision: 1,
79
+ platform: "instagram",
80
+ ruleRevision: "fixture-rules-1",
81
+ linkPlacement: "bio",
82
+ caption: "Fixture hook: puddles skin over early.\n\nFixture body: an earlier, shorter explainer about puddle rims.\n\n#fixturepuddles",
83
+ link: null,
84
+ createdAtUtc: "2026-09-22T09:00:00Z",
85
+ },
86
+ {
87
+ // Explainer, instagram, revision 2: the link goes in the bio, so the caption carries only the hashtags.
88
+ scopeId: SCOPE_ALPHA,
89
+ variantId: "var_3c1e0a9b7d5f4e21a6b08c9d7e5f4a31",
90
+ briefId: "explainer-puddle-edges",
91
+ briefRevision: 2,
92
+ platform: "instagram",
93
+ ruleRevision: "fixture-rules-1",
94
+ linkPlacement: "bio",
95
+ caption: "Fixture hook: puddles freeze at the edges first.\n\nFixture body: a short outdoor explainer about why a shallow puddle skins over at its rim.\n\n#fixturepuddles #fixturescience",
96
+ link: "https://links.example/fixture-puddles",
97
+ createdAtUtc: "2026-10-05T10:10:00Z",
98
+ },
99
+ {
100
+ // Explainer, youtube, revision 2: the link goes in the first comment, so the caption does not carry it.
101
+ scopeId: SCOPE_ALPHA,
102
+ variantId: "var_9f2a4d6c8e0b1a3c5f7d9b2e4a6c8d10",
103
+ briefId: "explainer-puddle-edges",
104
+ briefRevision: 2,
105
+ platform: "youtube",
106
+ ruleRevision: "fixture-rules-1",
107
+ linkPlacement: "first_comment",
108
+ caption: "Fixture hook: puddles freeze at the edges first.\n\nFixture body: a short outdoor explainer about why a shallow puddle skins over at its rim. Watch the full fixture episode.\n\n#fixturepuddles #fixturescience",
109
+ link: "https://links.example/fixture-puddles",
110
+ createdAtUtc: "2026-10-05T10:12:00Z",
111
+ },
112
+ {
113
+ // Demo, instagram, revision 1: no link was given, so the bio placement has no link to show.
114
+ scopeId: SCOPE_ALPHA,
115
+ variantId: "var_0d4b8f2a6c1e3b5d7f9a2c4e6b8d0f13",
116
+ briefId: "demo-kettle-setup",
117
+ briefRevision: 1,
118
+ platform: "instagram",
119
+ ruleRevision: "fixture-rules-1",
120
+ linkPlacement: "bio",
121
+ caption: "Fixture hook: set up the fixture kettle in three steps.\n\nFixture body: a product demo of a fictional kettle, filmed on one bench.\n\n#fixturekettle",
122
+ link: null,
123
+ createdAtUtc: "2026-10-03T10:00:00Z",
124
+ },
125
+ {
126
+ // Demo, linkedin, revision 1: the link sits in the caption, which the caption text shows.
127
+ scopeId: SCOPE_ALPHA,
128
+ variantId: "var_0d4b8f2a6c1e3b5d7f9a2c4e6b8d0f12",
129
+ briefId: "demo-kettle-setup",
130
+ briefRevision: 1,
131
+ platform: "linkedin",
132
+ ruleRevision: "fixture-rules-1",
133
+ linkPlacement: "caption",
134
+ caption: "Fixture hook: set up the fixture kettle in three steps.\n\nFixture body: a product demo of a fictional kettle, filmed on one bench.\n\nhttps://links.example/fixture-kettle\n\n#fixturekettle",
135
+ link: "https://links.example/fixture-kettle",
136
+ createdAtUtc: "2026-10-03T10:05:00Z",
137
+ },
138
+ ];
@@ -0,0 +1,6 @@
1
+ import type { Account, PublishJob } from "../types.js";
2
+ /** The daemon clock for the mock. Fixed so the fixtures always read the same. */
3
+ export declare const MOCK_NOW_UTC = "2026-10-12T18:30:00Z";
4
+ export declare const MOCK_POLICY_REVISION = 14;
5
+ export declare const FIXTURE_ACCOUNTS: Account[];
6
+ export declare const FIXTURE_JOBS: PublishJob[];
@@ -0,0 +1,420 @@
1
+ /** The daemon clock for the mock. Fixed so the fixtures always read the same. */
2
+ export const MOCK_NOW_UTC = "2026-10-12T18:30:00Z";
3
+ export const MOCK_POLICY_REVISION = 14;
4
+ const ALPHA = { id: "fixture-alpha", name: "Pip and Pebble (kids cartoon)" };
5
+ const BETA = { id: "fixture-beta", name: "Tidewater Tea (product brand)" };
6
+ export const FIXTURE_ACCOUNTS = [
7
+ {
8
+ id: "acc-pp-ig-main",
9
+ venture: ALPHA,
10
+ platform: "instagram",
11
+ handle: "@pipandpebble_kids",
12
+ displayName: "Pip and Pebble Kids",
13
+ idSuffix: "7F2A",
14
+ timeZone: "Europe/Lisbon",
15
+ band: 0,
16
+ readiness: {
17
+ token: { status: "valid", expiresOn: "2027-01-05" },
18
+ reauthNeededBy: null,
19
+ entitlement: "audience_visible",
20
+ quotaCooldownUntil: null,
21
+ quotaCooldownSource: null,
22
+ paused: null,
23
+ },
24
+ },
25
+ {
26
+ id: "acc-pp-ig-shorts",
27
+ venture: ALPHA,
28
+ platform: "instagram",
29
+ handle: "@pipandpebble_shorts",
30
+ displayName: "Pip and Pebble Shorts",
31
+ idSuffix: "9C41",
32
+ timeZone: "Europe/Lisbon",
33
+ band: 1,
34
+ readiness: {
35
+ token: { status: "expiring", expiresOn: "2026-10-14" },
36
+ reauthNeededBy: null,
37
+ entitlement: "review_submitted",
38
+ quotaCooldownUntil: "2026-10-13T06:20:00Z",
39
+ quotaCooldownSource: "dated_retry_after",
40
+ paused: {
41
+ by: "desktop",
42
+ atUtc: "2026-10-10T17:02:00Z",
43
+ reason: "Holding while the October caption review finishes",
44
+ },
45
+ },
46
+ },
47
+ {
48
+ id: "acc-pp-yt",
49
+ venture: ALPHA,
50
+ platform: "youtube",
51
+ handle: "@pipandpebble",
52
+ displayName: "Pip and Pebble",
53
+ idSuffix: "Q8R2",
54
+ timeZone: "Europe/Lisbon",
55
+ band: 2,
56
+ readiness: {
57
+ token: { status: "valid", expiresOn: "2027-01-05" },
58
+ reauthNeededBy: null,
59
+ entitlement: "sandbox_mechanics",
60
+ quotaCooldownUntil: null,
61
+ quotaCooldownSource: null,
62
+ paused: null,
63
+ },
64
+ },
65
+ {
66
+ id: "acc-tw-li",
67
+ venture: BETA,
68
+ platform: "linkedin",
69
+ handle: "@tidewater-tea-co",
70
+ displayName: "Tidewater Tea Co",
71
+ idSuffix: "L0W3",
72
+ timeZone: "America/New_York",
73
+ band: 3,
74
+ readiness: {
75
+ // Self-created LinkedIn apps get a 60-day token with no refresh (plan section 3).
76
+ token: { status: "valid", expiresOn: "2026-12-08" },
77
+ reauthNeededBy: "2026-12-08",
78
+ entitlement: "review_submitted",
79
+ quotaCooldownUntil: null,
80
+ quotaCooldownSource: null,
81
+ paused: null,
82
+ },
83
+ },
84
+ {
85
+ id: "acc-tw-ig",
86
+ venture: BETA,
87
+ platform: "instagram",
88
+ handle: "@tidewatertea",
89
+ displayName: "Tidewater Tea",
90
+ idSuffix: "3D7M",
91
+ timeZone: "America/New_York",
92
+ band: 4,
93
+ readiness: {
94
+ token: { status: "refresh_failed" },
95
+ reauthNeededBy: "2026-10-20",
96
+ entitlement: "audience_visible",
97
+ quotaCooldownUntil: null,
98
+ quotaCooldownSource: null,
99
+ paused: null,
100
+ },
101
+ },
102
+ {
103
+ id: "acc-pp-bsky",
104
+ venture: ALPHA,
105
+ platform: "bluesky",
106
+ handle: "@pipandpebble_bsky",
107
+ displayName: "Pip and Pebble Bluesky",
108
+ idSuffix: "B5K9",
109
+ timeZone: "Europe/Lisbon",
110
+ band: 5,
111
+ readiness: {
112
+ token: { status: "valid", expiresOn: "2027-02-01" },
113
+ reauthNeededBy: null,
114
+ entitlement: "sandbox_mechanics",
115
+ quotaCooldownUntil: null,
116
+ quotaCooldownSource: null,
117
+ paused: null,
118
+ },
119
+ },
120
+ {
121
+ id: "acc-tw-threads",
122
+ venture: BETA,
123
+ platform: "threads",
124
+ handle: "@tidewatertea_threads",
125
+ displayName: "Tidewater Tea Threads",
126
+ idSuffix: "T4H6",
127
+ timeZone: "America/New_York",
128
+ band: 1,
129
+ readiness: {
130
+ token: { status: "valid", expiresOn: "2027-02-01" },
131
+ reauthNeededBy: null,
132
+ entitlement: "sandbox_mechanics",
133
+ quotaCooldownUntil: null,
134
+ quotaCooldownSource: null,
135
+ paused: null,
136
+ },
137
+ },
138
+ ];
139
+ const b3 = (hex) => `blake3:${hex}`;
140
+ export const FIXTURE_JOBS = [
141
+ {
142
+ id: "job-1010",
143
+ accountId: "acc-pp-yt",
144
+ briefId: "brief-umbrella-song",
145
+ format: "short",
146
+ revision: 2,
147
+ caption: "Pip and Pebble: the umbrella song, sung backwards once. Watch the full version on the channel.",
148
+ captionDigest: "",
149
+ firstComment: null,
150
+ settings: ["Visibility: public", "Made for kids: yes"],
151
+ mediaItems: [
152
+ { hash: b3("d41f08a6c2e97b13"), kind: "video", durationSeconds: 47, dimensions: "1080x1920", status: "ready", uploadPercent: null },
153
+ ],
154
+ scheduledAtUtc: "2026-10-12T17:00:00Z",
155
+ timeZone: "Europe/Lisbon",
156
+ dst: { kind: "none" },
157
+ state: "published",
158
+ approval: { status: "approved", approvedRevision: 2, policyRevision: 14, approvedAtUtc: "2026-10-09T10:15:00Z" },
159
+ policyRevision: 14,
160
+ lateness: null,
161
+ conflict: null,
162
+ },
163
+ {
164
+ id: "job-1001",
165
+ accountId: "acc-pp-ig-main",
166
+ briefId: "brief-puddle-patrol-14",
167
+ format: "reel",
168
+ revision: 4,
169
+ caption: "Pip finds a snail that refuses to stop singing, so Pebble holds the umbrella for the whole duet.\n\nNew episode 14 of Puddle Patrol is out now.\n\n#kidsanimation #cartoon #puddlepatrol",
170
+ captionDigest: "",
171
+ firstComment: "Episode link: https://pipandpebble.example/puddle-patrol-14?utm_campaign=autumn_a",
172
+ settings: ["Visibility: public", "Alt text: present", "AI label: off"],
173
+ mediaItems: [
174
+ { hash: b3("9c1e4a7f0b2d6e83"), kind: "video", durationSeconds: 42, dimensions: "1080x1920", status: "ready", uploadPercent: null },
175
+ ],
176
+ scheduledAtUtc: "2026-10-13T08:00:00Z",
177
+ timeZone: "Europe/Lisbon",
178
+ dst: { kind: "none" },
179
+ state: "scheduled",
180
+ approval: { status: "approved", approvedRevision: 4, policyRevision: 14, approvedAtUtc: "2026-10-11T16:40:00Z" },
181
+ policyRevision: 14,
182
+ lateness: null,
183
+ conflict: null,
184
+ },
185
+ {
186
+ id: "job-1003",
187
+ accountId: "acc-pp-ig-shorts",
188
+ briefId: "brief-lighthouse-rules",
189
+ format: "reel",
190
+ revision: 3,
191
+ caption: "Shorts edition: the three rules of the Lighthouse Game, explained in 19 seconds.\n\n#shorts #kidsgames",
192
+ captionDigest: "",
193
+ firstComment: null,
194
+ settings: ["Visibility: public", "Alt text: present"],
195
+ mediaItems: [
196
+ { hash: b3("6b0ad39e5f12c7a4"), kind: "video", durationSeconds: 19, dimensions: "1080x1920", status: "ready", uploadPercent: null },
197
+ ],
198
+ scheduledAtUtc: "2026-10-13T08:30:00Z",
199
+ timeZone: "Europe/Lisbon",
200
+ dst: { kind: "none" },
201
+ state: "ready",
202
+ approval: { status: "pending" },
203
+ policyRevision: 14,
204
+ lateness: null,
205
+ conflict: { withJobId: "job-1004", minimumSpacingMinutes: 60 },
206
+ },
207
+ {
208
+ id: "job-1004",
209
+ accountId: "acc-pp-ig-shorts",
210
+ briefId: "brief-counting-game",
211
+ format: "reel",
212
+ revision: 1,
213
+ caption: "Pebble counts to ten and loses count at seven. Who can finish it?\n\nReply with your number.",
214
+ captionDigest: "",
215
+ firstComment: null,
216
+ settings: ["Visibility: public", "Alt text: present"],
217
+ mediaItems: [
218
+ { hash: b3("e72b5c01aa98d3f0"), kind: "video", durationSeconds: 24, dimensions: "1080x1920", status: "ready", uploadPercent: null },
219
+ ],
220
+ scheduledAtUtc: "2026-10-13T08:45:00Z",
221
+ timeZone: "Europe/Lisbon",
222
+ dst: { kind: "none" },
223
+ state: "ready",
224
+ approval: { status: "pending" },
225
+ policyRevision: 14,
226
+ lateness: null,
227
+ conflict: { withJobId: "job-1003", minimumSpacingMinutes: 60 },
228
+ },
229
+ {
230
+ id: "job-1002",
231
+ accountId: "acc-pp-ig-main",
232
+ briefId: "brief-bubble-bath",
233
+ format: "reel",
234
+ revision: 2,
235
+ caption: "Pebble's first bubble bath went about as well as you would expect. Pip is taking notes for the sequel.\n\nSound on. Episode link in bio.",
236
+ captionDigest: "",
237
+ firstComment: null,
238
+ settings: ["Visibility: public", "Alt text: present", "AI label: off"],
239
+ mediaItems: [
240
+ { hash: b3("4aa1c0de7b39f215"), kind: "video", durationSeconds: 37, dimensions: "1080x1920", status: "ready", uploadPercent: null },
241
+ ],
242
+ scheduledAtUtc: "2026-10-14T08:00:00Z",
243
+ timeZone: "Europe/Lisbon",
244
+ dst: { kind: "none" },
245
+ state: "ready",
246
+ approval: { status: "pending" },
247
+ policyRevision: 14,
248
+ lateness: null,
249
+ conflict: null,
250
+ },
251
+ {
252
+ id: "job-1011",
253
+ accountId: "acc-pp-ig-shorts",
254
+ briefId: "brief-friday-secret",
255
+ format: "reel",
256
+ revision: 4,
257
+ caption: "Pebble's new episode drops Friday with a secret ending. Set a reminder.",
258
+ captionDigest: "",
259
+ firstComment: null,
260
+ settings: ["Visibility: public", "Alt text: present"],
261
+ mediaItems: [
262
+ { hash: b3("0f7de23ac4b81e96"), kind: "video", durationSeconds: 31, dimensions: "1080x1920", status: "ready", uploadPercent: null },
263
+ ],
264
+ scheduledAtUtc: "2026-10-14T11:00:00Z",
265
+ timeZone: "Europe/Lisbon",
266
+ dst: { kind: "none" },
267
+ state: "draft",
268
+ approval: {
269
+ status: "rejected",
270
+ reason: "wrong_content",
271
+ note: "Caption promises a Friday episode that is not on the schedule.",
272
+ },
273
+ policyRevision: 14,
274
+ lateness: null,
275
+ conflict: null,
276
+ },
277
+ {
278
+ id: "job-1005",
279
+ accountId: "acc-pp-ig-main",
280
+ briefId: "brief-storyboard-room",
281
+ format: "carousel",
282
+ revision: 1,
283
+ caption: "Five drawings from the storyboard room, in order. Which one should Pip fix first?",
284
+ captionDigest: "",
285
+ firstComment: null,
286
+ settings: ["Visibility: public", "Alt text: present"],
287
+ mediaItems: [
288
+ { hash: b3("a3c7f019d0e52b48"), kind: "image", durationSeconds: null, dimensions: "1080x1350", status: "ready", uploadPercent: null },
289
+ { hash: b3("b58e2d6f1a093c7e"), kind: "image", durationSeconds: null, dimensions: "1080x1350", status: "ready", uploadPercent: null },
290
+ { hash: b3("c19f4a8e2d7b605f"), kind: "image", durationSeconds: null, dimensions: "1080x1350", status: "uploading", uploadPercent: 60 },
291
+ ],
292
+ scheduledAtUtc: "2026-10-15T17:00:00Z",
293
+ timeZone: "Europe/Lisbon",
294
+ dst: { kind: "none" },
295
+ state: "media_pending",
296
+ approval: { status: "none" },
297
+ policyRevision: 14,
298
+ lateness: null,
299
+ conflict: null,
300
+ },
301
+ {
302
+ id: "job-1006",
303
+ accountId: "acc-pp-yt",
304
+ briefId: "brief-seesaw-cut",
305
+ format: "short",
306
+ revision: 3,
307
+ caption: "Pebble on the seesaw. Full cut on the channel.",
308
+ captionDigest: "",
309
+ firstComment: null,
310
+ settings: ["Visibility: public", "Made for kids: yes"],
311
+ mediaItems: [
312
+ { hash: b3("7d2a91c4be05f838"), kind: "video", durationSeconds: 58, dimensions: "1080x1920", status: "uploading", uploadPercent: 40 },
313
+ ],
314
+ scheduledAtUtc: "2026-10-16T11:00:00Z",
315
+ timeZone: "Europe/Lisbon",
316
+ dst: { kind: "none" },
317
+ state: "late",
318
+ approval: { status: "approved", approvedRevision: 3, policyRevision: 14, approvedAtUtc: "2026-10-14T09:02:00Z" },
319
+ policyRevision: 14,
320
+ lateness: { mediaNotReadyBy: "2026-10-16T10:40:00Z", policy: "publish_next_slot" },
321
+ conflict: null,
322
+ },
323
+ {
324
+ id: "job-1007",
325
+ accountId: "acc-tw-ig",
326
+ briefId: "brief-cold-brew-sampler",
327
+ format: "photo",
328
+ revision: 5,
329
+ caption: "New Tidewater Tea Co. Cold Brew Sampler is in stock.\n\nFour blends, one tin, no bitterness at room temperature. The link in bio has the sampler and shipping times.",
330
+ captionDigest: "",
331
+ firstComment: null,
332
+ settings: ["Visibility: public", "Alt text: present", "Partnership label: off"],
333
+ mediaItems: [
334
+ { hash: b3("e8f30b5a2c7d19e4"), kind: "image", durationSeconds: null, dimensions: "1080x1350", status: "ready", uploadPercent: null },
335
+ ],
336
+ scheduledAtUtc: "2026-10-12T09:00:00Z",
337
+ timeZone: "America/New_York",
338
+ dst: { kind: "none" },
339
+ state: "outcome_unknown",
340
+ approval: { status: "approved", approvedRevision: 5, policyRevision: 14, approvedAtUtc: "2026-10-12T13:10:00Z" },
341
+ policyRevision: 14,
342
+ lateness: null,
343
+ conflict: null,
344
+ },
345
+ {
346
+ id: "job-1008",
347
+ accountId: "acc-tw-li",
348
+ briefId: "brief-recyclable-tins",
349
+ format: "text_post",
350
+ revision: 6,
351
+ caption: "Tidewater Tea Co. is moving the whole cold brew line to recyclable tins this autumn. Here is what changes for wholesale customers, what does not, and the new lead times.",
352
+ captionDigest: "",
353
+ firstComment: null,
354
+ settings: ["Visibility: public"],
355
+ mediaItems: [
356
+ { hash: b3("1b9c6e4f0a58d2c7"), kind: "image", durationSeconds: null, dimensions: "1200x627", status: "ready", uploadPercent: null },
357
+ ],
358
+ scheduledAtUtc: "2026-10-18T12:30:00Z",
359
+ timeZone: "America/New_York",
360
+ dst: { kind: "none" },
361
+ state: "draft",
362
+ approval: {
363
+ status: "invalidated",
364
+ approvedRevision: 5,
365
+ policyRevision: 14,
366
+ cause: "caption_changed",
367
+ },
368
+ policyRevision: 14,
369
+ lateness: null,
370
+ conflict: null,
371
+ },
372
+ {
373
+ id: "job-1012",
374
+ accountId: "acc-tw-li",
375
+ briefId: "brief-cold-brew-science",
376
+ format: "text_post",
377
+ revision: 1,
378
+ caption: "Our head blender on why cold brew needs longer than you think. Eighteen hours, and why the clock matters.",
379
+ captionDigest: "",
380
+ firstComment: null,
381
+ settings: ["Visibility: public"],
382
+ mediaItems: [
383
+ { hash: b3("2f6d0a9c7e1b4538"), kind: "image", durationSeconds: null, dimensions: "1200x627", status: "ready", uploadPercent: null },
384
+ ],
385
+ scheduledAtUtc: "2026-10-21T13:00:00Z",
386
+ timeZone: "America/New_York",
387
+ dst: { kind: "none" },
388
+ state: "ready",
389
+ approval: { status: "pending" },
390
+ policyRevision: 14,
391
+ lateness: null,
392
+ conflict: null,
393
+ },
394
+ {
395
+ id: "job-1009",
396
+ accountId: "acc-pp-ig-main",
397
+ briefId: "brief-orchestra-puddle",
398
+ format: "reel",
399
+ revision: 2,
400
+ caption: "Sunday trick: Pebble tries to fit the whole orchestra in one puddle. The puddle wins.\n\nEpisode 15 lands soon.",
401
+ captionDigest: "",
402
+ firstComment: null,
403
+ settings: ["Visibility: public", "Alt text: present", "AI label: off"],
404
+ mediaItems: [
405
+ { hash: b3("83a5f1d2c0e7946b"), kind: "video", durationSeconds: 30, dimensions: "1080x1920", status: "ready", uploadPercent: null },
406
+ ],
407
+ // 01:30 on 2026-10-25 happens twice in Europe/Lisbon. The first occurrence is +01:00 (00:30 UTC).
408
+ scheduledAtUtc: "2026-10-25T00:30:00Z",
409
+ timeZone: "Europe/Lisbon",
410
+ dst: {
411
+ kind: "ambiguous_fires_once",
412
+ note: "01:30 happens twice on this night. Fires once, at the first occurrence (+01:00).",
413
+ },
414
+ state: "scheduled",
415
+ approval: { status: "approved", approvedRevision: 2, policyRevision: 14, approvedAtUtc: "2026-10-12T09:30:00Z" },
416
+ policyRevision: 14,
417
+ lateness: null,
418
+ conflict: null,
419
+ },
420
+ ];
@@ -0,0 +1,98 @@
1
+ import type { DataSource, MockControls, Result } from "../source.js";
2
+ import type { Account, AccountDisconnect, Campaign, CampaignAttribution, ChannelLink, DateRange, Digest, FailureItem, FailureResolution, HookTestSet, ManualDraft, ModerationCapability, OperatorScope, OptimiseProposal, PublishJob, PublishJobRecord, ReconcileResult, RejectDecision, SpendAllowance, SpendPrice, SpendReport, ThreadItem, TrackedLink, ContentDetail, ContentExperimentSummary, ContentRecord, DigestApplication, ResearchDigest, DeliveryEnvelope, DispatchAttempt, GateCheck, GatePolicy, GateRecord, OutgoingBrief, OutgoingBriefDetail, ChannelMemoryDetail, ChannelMemoryRevision, ProofRecord, ProofStatus, TypedBrief, BriefVariant, AnalyticsObservation, AnalyticsPost, MetricImport, HeartbeatStatus, RestoreReceiptList, InboxListing, InboxRaw, InboxStats, PublishNextView, PublishQuota } from "../types.js";
3
+ export interface MockOptions {
4
+ /** Simulated network latency for every call. Default 220 ms, so loading states are visible. */
5
+ latencyMs?: number;
6
+ /** When true, every read rejects. Lets the error state be exercised. */
7
+ failReads?: boolean;
8
+ }
9
+ /** FNV-1a, 32 bit. A display fingerprint for the mock, not a cryptographic digest. */
10
+ export declare function fingerprint(text: string): string;
11
+ /**
12
+ * In-memory daemon stand-in. It enforces the daemon's rules on every write: a mismatched account
13
+ * or revision returns a typed error and changes nothing. Data resets on each page load.
14
+ */
15
+ export declare class MockDataSource implements DataSource, MockControls {
16
+ readonly kind: "mock";
17
+ readonly mock: MockControls;
18
+ private readonly latencyMs;
19
+ private readonly failReads;
20
+ private readonly accounts;
21
+ private readonly jobs;
22
+ private readonly failures;
23
+ private readonly threads;
24
+ private readonly drafts;
25
+ private readonly proposals;
26
+ constructor(options?: MockOptions);
27
+ nowUtc(): string;
28
+ listAccounts(): Promise<Account[]>;
29
+ listJobs(range: DateRange): Promise<PublishJob[]>;
30
+ getJob(jobId: string): Promise<PublishJob | null>;
31
+ approve(jobId: string, revision: number, accountId: string): Promise<Result<PublishJob>>;
32
+ reject(jobId: string, decision: RejectDecision): Promise<Result<PublishJob>>;
33
+ pauseAccount(accountId: string, reason: string): Promise<Result<Account>>;
34
+ resumeAccount(accountId: string): Promise<Result<Account>>;
35
+ disconnectAccount(accountId: string): Promise<Result<AccountDisconnect>>;
36
+ simulateCaptionChange(jobId: string, caption: string): Promise<Result<PublishJob>>;
37
+ listFailures(): Promise<FailureItem[]>;
38
+ reconcile(jobId: string): Promise<Result<ReconcileResult>>;
39
+ resolveFailure(failureId: string, resolution: FailureResolution, note: string): Promise<Result<FailureItem>>;
40
+ listDigest(): Promise<Digest>;
41
+ listThreads(): Promise<ThreadItem[]>;
42
+ resolveThread(threadId: string): Promise<Result<ThreadItem>>;
43
+ snoozeThread(threadId: string, untilUtc: string): Promise<Result<ThreadItem>>;
44
+ listModerationCapabilities(): Promise<ModerationCapability[]>;
45
+ listDrafts(): Promise<ManualDraft[]>;
46
+ approveDraft(draftId: string, revision: number, disclosurePhrases: readonly string[]): Promise<Result<ManualDraft>>;
47
+ markDraftPosted(draftId: string, revision: number, postedUrl: string): Promise<Result<ManualDraft>>;
48
+ skipDraft(draftId: string, revision: number, reason: string): Promise<Result<ManualDraft>>;
49
+ listScopes(): Promise<OperatorScope[]>;
50
+ listProposals(scopeId: string): Promise<OptimiseProposal[]>;
51
+ getProposal(scopeId: string, proposalId: string): Promise<OptimiseProposal | null>;
52
+ acceptProposal(scopeId: string, proposalId: string, expectedRevision: number): Promise<Result<OptimiseProposal>>;
53
+ dismissProposal(scopeId: string, proposalId: string, expectedRevision: number): Promise<Result<OptimiseProposal>>;
54
+ getAllowance(scopeId: string, month: string): Promise<SpendAllowance | null>;
55
+ spendReport(scopeId: string, month: string): Promise<SpendReport | null>;
56
+ listPrices(): Promise<SpendPrice[]>;
57
+ listCampaigns(scopeId: string): Promise<Campaign[]>;
58
+ listCampaignLinks(scopeId: string, campaignId: string): Promise<TrackedLink[]>;
59
+ campaignAttribution(scopeId: string, campaignId: string): Promise<CampaignAttribution | null>;
60
+ listChannelLinks(scopeId: string): Promise<ChannelLink[]>;
61
+ listHookTests(scopeId: string): Promise<HookTestSet[]>;
62
+ getHookTest(scopeId: string, setId: string): Promise<HookTestSet | null>;
63
+ listDigests(scopeId: string): Promise<ResearchDigest[]>;
64
+ getDigest(scopeId: string, digestId: string): Promise<ResearchDigest | null>;
65
+ digestApplications(scopeId: string, digestId: string): Promise<DigestApplication[] | null>;
66
+ listContent(scopeId: string): Promise<ContentRecord[]>;
67
+ getContent(scopeId: string, contentId: string): Promise<ContentDetail | null>;
68
+ contentExperimentSummary(scopeId: string, contentId: string): Promise<ContentExperimentSummary | null>;
69
+ gatePolicy(scopeId: string): Promise<GatePolicy | null>;
70
+ checkText(scopeId: string, subjectId: string, text: string): Promise<Result<GateCheck>>;
71
+ gateRecords(scopeId: string, subjectId: string): Promise<GateRecord[]>;
72
+ listBriefs(scopeId: string): Promise<OutgoingBrief[]>;
73
+ getBrief(scopeId: string, requestId: string): Promise<OutgoingBriefDetail | null>;
74
+ listDeliveries(scopeId: string): Promise<DeliveryEnvelope[]>;
75
+ getDelivery(scopeId: string, envelopeId: string): Promise<DeliveryEnvelope | null>;
76
+ listDispatchAttempts(scopeId: string, requestId: string): Promise<DispatchAttempt[]>;
77
+ channelMemory(scopeId: string): Promise<ChannelMemoryDetail | null>;
78
+ channelMemoryHistory(scopeId: string): Promise<ChannelMemoryRevision[]>;
79
+ listProofs(scopeId: string, status: ProofStatus | null): Promise<ProofRecord[]>;
80
+ listTypedBriefs(scopeId: string): Promise<TypedBrief[]>;
81
+ getTypedBrief(scopeId: string, briefId: string): Promise<TypedBrief | null>;
82
+ listBriefVariants(scopeId: string, briefId: string): Promise<BriefVariant[]>;
83
+ listAnalyticsPosts(scopeId: string): Promise<AnalyticsPost[]>;
84
+ listAnalyticsObservations(scopeId: string, jobId: string): Promise<AnalyticsObservation[]>;
85
+ listMetricImports(scopeId: string): Promise<MetricImport[]>;
86
+ listPublishJobs(scopeId: string): Promise<PublishJobRecord[]>;
87
+ listInbox(scopeId: string | null): Promise<InboxListing>;
88
+ getInboxEvent(scopeId: string | null, eventId: string): Promise<InboxRaw | null>;
89
+ inboxStats(): Promise<InboxStats>;
90
+ listPublishNext(scopeId: string): Promise<PublishNextView>;
91
+ getPublishQuota(scopeId: string, platform: string, remoteAccount: string): Promise<PublishQuota>;
92
+ heartbeatStatus(nowUtc: string, staleAfterSecs: number): Promise<HeartbeatStatus>;
93
+ listRestoreReceipts(): Promise<RestoreReceiptList>;
94
+ private findProposal;
95
+ private isPaused;
96
+ private read;
97
+ private delay;
98
+ }