@sellable/mcp 0.1.255 → 0.1.257

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.
@@ -28,10 +28,11 @@ Worker owns:
28
28
  - tracked-person post fetches
29
29
  - full-text matching by URL/activity ID
30
30
  - duplicate removal
31
+ - follower-band and reach-normalized signal scoring
31
32
  - lead-magnet, giveaway, engagement-bait, and off-voice filtering
32
33
  - market belief mapping across kept and rejected examples
33
34
  - premise input extraction: real scenes, observed tensions, reader value, proof gaps
34
- - hook opening measurement
35
+ - rendered hook opening measurement
35
36
  - exact phrase-pattern extraction
36
37
  - body-structure extraction
37
38
  - rejected-example notes
@@ -58,7 +59,8 @@ Research packet:
58
59
  - exact phrase patterns: max 20
59
60
  - body patterns: max 8
60
61
  - source URLs and author profile URLs
61
- - preview measurements
62
+ - reach-normalized signal notes
63
+ - rendered preview records
62
64
  - confidence gaps
63
65
  - save recommendations
64
66
  ```
@@ -74,6 +76,13 @@ Use `mcp__sellable__search_engagement_posts` with practical constraints:
74
76
 
75
77
  - explicit `maxAgeDays: 120` by default so research covers the past 30-120 days, not only this week's posts
76
78
  - tighten to 30-60 days for trend-sensitive topics; widen only when the topic has low volume
79
+ - when the user gives a follower range, pass it as `targetFollowerMin` and
80
+ `targetFollowerMax` so the tool can expose reach-normalized signals and
81
+ prioritize comparable creators
82
+ - if a source hook looks promising but follower count is missing, use
83
+ `mcp__sellable__fetch_linkedin_profile` on a bounded shortlist before calling
84
+ the source a keeper; if profile fetch is unavailable or too slow, mark
85
+ `follower_count_unavailable`
77
86
  - high enough engagement to matter
78
87
  - narrow enough to match the idea
79
88
  - enough result depth to see whether a creator has repeated winners, not just one viral outlier
@@ -82,14 +91,19 @@ Record every keyword, filter, search window, page, and result count.
82
91
 
83
92
  ## Weighted Signals
84
93
 
85
- Rank source posts with a weighted signal view. Do not sort by total engagement alone.
94
+ Rank source posts with a weighted signal view. Do not sort by total engagement
95
+ alone. Use the numeric score to decide which sources deserve study; do not let
96
+ the number choose the final hook by itself.
86
97
 
87
98
  Use this scoring shape:
88
99
 
89
100
  ```text
90
101
  hook_score =
91
102
  topic_fit
103
+ + rendered_mobile_preview_strength
92
104
  + hook_clarity
105
+ + content_pattern_replicability
106
+ + reach_adjusted_evidence
93
107
  + creator_repeat_success
94
108
  + repost_share_strength
95
109
  + comment_quality
@@ -107,6 +121,135 @@ Guidance:
107
121
  - If share or repost data is unavailable, record `repost_data_unavailable` instead of inventing it.
108
122
  - Prefer creators with repeated high-performing posts in the same lane over one-off viral posts.
109
123
 
124
+ ## Reach-Normalized Hook Scoring
125
+
126
+ Raw engagement is not the clearest signal when source creators have wildly
127
+ different audience sizes. A post from a 100k+ follower creator can win on
128
+ distribution even when the hook is ordinary. A post from a creator near the
129
+ user's follower range that overperforms is a clearer signal that LinkedIn and
130
+ readers rewarded the hook/premise.
131
+
132
+ This calculation is a source-quality filter, not the final creative decision.
133
+ After reach is controlled, the LLM still chooses the hooks to steal based on the
134
+ visible hook, rendered first-screen promise, content pattern, premise fit, and
135
+ whether the idea can carry without the source creator's distribution.
136
+
137
+ When the user gives a target range, default to that range. For example, if the
138
+ user says "8k to 20k followers," call `mcp__sellable__search_engagement_posts`
139
+ with:
140
+
141
+ ```text
142
+ targetFollowerMin: 8000
143
+ targetFollowerMax: 20000
144
+ ```
145
+
146
+ If the user gives no range, use the user's known follower count from memory or
147
+ ask only when the decision depends on it. If no follower count is available, use
148
+ raw engagement as a fallback and mark `target_follower_band_unknown`.
149
+
150
+ For every shortlisted source post, record:
151
+
152
+ - `authorFollowerCount`
153
+ - `targetFollowerBand`
154
+ - `followerBandFit`: `in_target_band`, `below_target_band`,
155
+ `above_target_band`, or `unknown`
156
+ - `engagementPer1kFollowers`
157
+ - `weightedEngagementPer1kFollowers`
158
+ - `reachAdjustedScore`
159
+ - `creatorBaselineMedianEngagement` when repeated posts from that creator are
160
+ available
161
+ - `baselineLift`: this post's engagement divided by the creator's recent median
162
+ engagement in the same lane, or `creator_baseline_unavailable`
163
+ - `confidence`: `high`, `medium`, or `low`
164
+ - `normalizationNotes`
165
+
166
+ Use this Harvest-compatible calculation when follower counts are available:
167
+
168
+ ```text
169
+ weightedEngagement =
170
+ likes
171
+ + (comments * 4)
172
+ + (shares * 12)
173
+
174
+ engagementPer1kFollowers =
175
+ totalEngagement / authorFollowerCount * 1000
176
+
177
+ weightedEngagementPer1kFollowers =
178
+ weightedEngagement / authorFollowerCount * 1000
179
+
180
+ reachPenaltyMultiplier =
181
+ 1.00 when in target follower band
182
+ 0.75 when below target band
183
+ 0.65 when above target band but <= 2x target max
184
+ 0.35 when above target band but <= 5x target max
185
+ 0.15 when above target band and > 5x target max
186
+ 0.40 when follower count is unknown
187
+
188
+ reachAdjustedScore =
189
+ weightedEngagementPer1kFollowers * reachPenaltyMultiplier
190
+ ```
191
+
192
+ When repeated posts from the same creator are available, calculate:
193
+
194
+ ```text
195
+ creatorBaselineMedianEngagement =
196
+ median weightedEngagement across recent same-lane posts
197
+
198
+ baselineLift =
199
+ post weightedEngagement / creatorBaselineMedianEngagement
200
+ ```
201
+
202
+ Use this scoring shape when deciding which hooks to study:
203
+
204
+ ```text
205
+ hook_score =
206
+ topic_fit
207
+ + rendered_mobile_preview_strength
208
+ + hook_clarity
209
+ + same_follower_band_bonus
210
+ + weighted_engagement_per_1k_followers
211
+ + creator_baseline_lift
212
+ + repost_share_strength
213
+ + substantive_comment_quality
214
+ + creator_repeat_success
215
+ - celebrity_reach_penalty
216
+ - tiny_sample_penalty
217
+ - lead_magnet_penalty
218
+ - engagement_bait_penalty
219
+ - off_voice_penalty
220
+ ```
221
+
222
+ Rules:
223
+
224
+ - Prefer in-band creators when the hook is strong and the engagement is real.
225
+ - Use near-band creators as secondary evidence.
226
+ - Penalize creators far above the target range, especially accounts above 5x
227
+ the target max or above 100k followers when the target range is 8k-20k.
228
+ - Do not automatically reject large-account posts; they can still teach body
229
+ structure, topic heat, or category language. Do not treat them as primary hook
230
+ proof unless they also overperform their creator baseline.
231
+ - Large-account hooks can be selected only when the hook carries without the
232
+ creator: the rendered mobile opening is strong, the premise is reusable by the
233
+ user, the body pattern does not depend on celebrity authority, and the source
234
+ either beats baseline or has unusually high reach-adjusted quality after the
235
+ penalty.
236
+ - Do not overvalue tiny-account anomalies. Very high engagement per follower
237
+ with low absolute engagement is interesting but lower confidence.
238
+ - When follower data is unavailable, do not invent it. Mark
239
+ `follower_count_unavailable`, lower confidence, and rely more on creator repeat
240
+ evidence, repost/share strength, comment quality, and rendered preview quality.
241
+ - A source hook is strongest when it is in/near the target follower band, has
242
+ real absolute engagement, beats the creator's apparent baseline, has
243
+ share/comment quality, and passes rendered mobile preview.
244
+
245
+ Final hook selection must explain both:
246
+
247
+ - `whyTheHookCarries`: the visible words, specificity, tension, and first-screen
248
+ promise that work independent of reach
249
+ - `whyTheReachEvidenceIsTrustworthy`: follower-band fit, reach-adjusted score,
250
+ baseline lift, share/comment quality, or why a large-account example is only
251
+ secondary pattern evidence
252
+
110
253
  Penalize lead-magnet and engagement-bait mechanics unless the user explicitly asks for that style:
111
254
 
112
255
  - `comment "template"` / `comment "guide"` / `comment "playbook"`
@@ -198,24 +341,48 @@ Measure the visible opening for every shortlisted source post before extracting
198
341
  the hook pattern. This makes the study useful for LinkedIn, not just generally
199
342
  "good writing."
200
343
 
201
- LinkedIn does not publish exact "see more" cutoff rules. Treat these as
202
- conservative v1 planning budgets:
344
+ Use `references/linkedin-preview-rendering.md`. LinkedIn does not publish exact
345
+ "see more" cutoff rules. Character counts are diagnostics only. A source post
346
+ or generated hook is not properly studied until it has a rendered mobile and
347
+ desktop preview record.
348
+
349
+ Default deterministic renderer for unpublished drafts and research comparison:
203
350
 
204
- - `pass`: opening hook is <= 110 chars including newlines, every nonblank line
205
- is <= 45 chars, and the hook's core point lands before likely truncation.
206
- - `warn`: opening hook is 111-140 chars including newlines, any nonblank line is
207
- 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed,
208
- but they count as physical lines.
209
- - `fail`: opening hook is > 140 chars including newlines, any nonblank line is
210
- > 55 chars, or the hook's core point depends on text after likely truncation.
351
+ ```text
352
+ cssContractVersion: linkedin-preview-rendering/v1
353
+ font size: 14px
354
+ line height: 21px
355
+ white space: pre-wrap
356
+ overflow wrap: break-word
357
+ mobile text width: 308px
358
+ desktop text width: 582px
359
+ review clamp: first 3 rendered text lines
360
+ ```
211
361
 
212
- Desktop preview has more room, so record it separately, but never let desktop
213
- fit compensate for a mobile `fail`.
362
+ Authenticated LinkedIn feed/composer screenshots are stronger evidence when
363
+ available. If a screenshot is used, still record the wrapped lines and verdicts
364
+ below so another agent can compare hooks without re-opening LinkedIn.
365
+
366
+ Desktop preview has more room, but never let desktop fit compensate for a
367
+ mobile `fail`.
214
368
 
215
369
  For each source, record:
216
370
 
217
371
  - `sourceTextBasis`: `full_text`, `search_preview`, or `manual_user_source`
218
372
  - `openingTextUsed`
373
+ - `renderedPreview`
374
+ - `cssContractVersion`
375
+ - `mobileRenderedPreviewBlock`
376
+ - `desktopRenderedPreviewBlock`
377
+ - `mobileRenderedLines`
378
+ - `desktopRenderedLines`
379
+ - `mobileRenderedLineCount`
380
+ - `desktopRenderedLineCount`
381
+ - `corePainProofOrCuriosityVisibleMobile`
382
+ - `corePainProofOrCuriosityVisibleDesktop`
383
+ - `corePointVisibleMobile`
384
+ - `corePointVisibleDesktop`
385
+ - `pointAfterMobileClamp`
219
386
  - `charCountIncludingNewlines`
220
387
  - `physicalLineCount`
221
388
  - `contentLineCount`
@@ -228,11 +395,25 @@ For each source, record:
228
395
  - `desktopPreviewBudget`: `pass`, `warn`, or `fail`
229
396
  - `blankLineVisualRisk`
230
397
  - `corePointBeforeLikelyTruncation`
398
+ - `renderedPreviewVerdict`: `pass`, `warn`, or `fail`
231
399
 
232
400
  If only a search preview is available, do not pretend the opening is complete.
233
401
  Record `sourceTextBasis: search_preview` and lower confidence when the hook
234
402
  appears cut off or body context is unavailable.
235
403
 
404
+ Pass/warn/fail is based on rendered output:
405
+
406
+ - `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
407
+ the end of the first 3 rendered lines, and the core point is understandable
408
+ without opening "see more".
409
+ - `warn`: the mobile rendered preview creates useful curiosity but the core
410
+ point is slightly softened by wrapping, blank-line rhythm, or one missing
411
+ context word. A compact fallback is required.
412
+ - `fail`: the hook's real point appears after the first 3 mobile rendered
413
+ lines, the first rendered line is generic setup, blank lines consume the
414
+ preview before the reader sees the point, or desktop fit is the only reason it
415
+ looks good.
416
+
236
417
  ## Hook Extraction
237
418
 
238
419
  Extract structure and reusable language patterns, not copied prose. The goal is
@@ -245,10 +426,13 @@ For each shortlisted source post, record:
245
426
  - URL
246
427
  - author
247
428
  - author profile URL when available
429
+ - author follower count and follower-band fit when available
248
430
  - engagement totals and available likes/comments/shares breakdown
431
+ - reach-normalized metrics: engagement per 1k followers, weighted engagement
432
+ per 1k followers, reach-adjusted score, baseline lift, and confidence
249
433
  - creator repeat evidence
250
434
  - visible hook text or preview
251
- - opening preview measurement fields from the section above
435
+ - rendered preview fields from the section above
252
436
  - hook mechanism
253
437
  - exact hook language patterns: reusable words, phrase shapes, contrast forms,
254
438
  sentence shapes, and transition moves
@@ -258,10 +442,16 @@ For each shortlisted source post, record:
258
442
  - proof/story dependency
259
443
  - lead magnet or engagement bait penalty
260
444
  - weighted signal notes
445
+ - reach-normalized signal notes
261
446
  - replicability score
262
447
  - track person recommendation: `yes`, `no`, or `ask_user`
263
448
  - tracking reason when recommended
264
449
 
450
+ Do not call a source hook a keeper because the full text is strong if the
451
+ rendered mobile opening is weak. Study what a LinkedIn reader sees before
452
+ "see more"; the body structure can still be useful, but the hook should not be
453
+ copied into the hook pattern set.
454
+
265
455
  ## Specific Language Extraction
266
456
 
267
457
  For each keeper, extract the source's specific language mechanics in this
@@ -0,0 +1,163 @@
1
+ # LinkedIn Preview Rendering
2
+
3
+ This asset defines the required rendered-preview contract for create-post hook
4
+ research, hook candidate generation, gold-standard decomposition, and draft
5
+ validation.
6
+
7
+ Character budgets are only diagnostics. They are not the preview gate. A hook is
8
+ not studied, selected, or ready until it has been rendered through this contract
9
+ or through a stricter authenticated LinkedIn screenshot.
10
+
11
+ ## Rendering Basis
12
+
13
+ LinkedIn does not publish exact feed truncation rules, and rendering can vary by
14
+ surface, app version, device, media attachment, and account state. Use this
15
+ deterministic renderer as the MCP review gate for unpublished drafts.
16
+
17
+ When an authenticated LinkedIn feed/composer/browser screenshot is available,
18
+ that screenshot is the strongest evidence. Still record the fields below so
19
+ future agents can compare candidates without redoing the visual inspection.
20
+
21
+ Observed public LinkedIn post text style for feed-style post pages:
22
+
23
+ ```text
24
+ selector basis: p.attributed-text-segment-list__content
25
+ font family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue",
26
+ "Fira Sans", Ubuntu, "Oxygen Sans", Cantarell, "Droid Sans",
27
+ "Lucida Grande", Helvetica, Arial, sans-serif
28
+ font size: 14px
29
+ line height: 21px
30
+ font weight: 400
31
+ letter spacing: normal
32
+ white space: pre-wrap
33
+ overflow wrap: break-word
34
+ text color: rgba(0, 0, 0, 0.9)
35
+ mobile text width: 308px
36
+ desktop text width: 582px
37
+ review clamp: first 3 rendered text lines
38
+ ```
39
+
40
+ The `review clamp` is not an official LinkedIn rule. It is the conservative
41
+ apples-to-apples region create-post must use when comparing hooks. Do not let a
42
+ desktop pass rescue a mobile fail.
43
+
44
+ ## Required Rendering Record
45
+
46
+ Every shortlisted source hook, adapted hook block, generated hook candidate, and
47
+ selected hook must include a `renderedPreview` record:
48
+
49
+ ```text
50
+ renderedPreview:
51
+ basis: linkedin_css_contract | authenticated_linkedin_screenshot | manual_user_source
52
+ cssContractVersion: linkedin-preview-rendering/v1
53
+ mobile:
54
+ textWidthPx: 308
55
+ fontSizePx: 14
56
+ lineHeightPx: 21
57
+ visibleTextBlock: <literal first rendered review-clamp block>
58
+ renderedLines:
59
+ - <line 1 exactly as wrapped>
60
+ - <line 2 exactly as wrapped>
61
+ - <line 3 exactly as wrapped>
62
+ lineCountBeforeClamp: <number>
63
+ blankLinesBeforeClamp: <number>
64
+ corePainProofOrCuriosityVisible: true | false
65
+ corePointVisible: true | false
66
+ seeMoreRisk: pass | warn | fail
67
+ screenshotPath: <optional local path>
68
+ desktop:
69
+ textWidthPx: 582
70
+ fontSizePx: 14
71
+ lineHeightPx: 21
72
+ visibleTextBlock: <literal first rendered review-clamp block>
73
+ renderedLines:
74
+ - <line 1 exactly as wrapped>
75
+ - <line 2 exactly as wrapped>
76
+ - <line 3 exactly as wrapped>
77
+ lineCountBeforeClamp: <number>
78
+ blankLinesBeforeClamp: <number>
79
+ corePainProofOrCuriosityVisible: true | false
80
+ corePointVisible: true | false
81
+ seeMoreRisk: pass | warn | fail
82
+ screenshotPath: <optional local path>
83
+ diagnostics:
84
+ charCount: <number>
85
+ charCountIncludingNewlines: <number>
86
+ firstLineChars: <number>
87
+ firstTwoPhysicalLinesChars: <number>
88
+ longestNonblankLineChars: <number>
89
+ blankLineVisualRisk: none | low | medium | high
90
+ pointAfterMobileClamp: true | false
91
+ rewriteIfTruncated: <short fallback>
92
+ ```
93
+
94
+ If a host cannot produce screenshots, it must still produce the literal wrapped
95
+ line blocks using the CSS contract. If it cannot produce either screenshots or
96
+ literal line wraps, return `blocked` or `needs_revision`; do not claim the hook
97
+ passed preview validation from character counts alone.
98
+
99
+ ## Study Rules
100
+
101
+ For source-post research:
102
+
103
+ - Render the exact visible opening from full text when full text is available.
104
+ - If only a search preview is available, render only the preview text and set
105
+ `basis: manual_user_source` or record `sourceTextBasis: search_preview`.
106
+ - Lower confidence when the search preview is cut off or the body is
107
+ unavailable.
108
+ - Extract hook lessons from the rendered first-screen experience, not from the
109
+ full post in isolation.
110
+
111
+ For generated hooks:
112
+
113
+ - Generate the hook from the selected premise first.
114
+ - Render the hook for mobile and desktop before scoring it.
115
+ - Score the rendered first-screen promise before scoring cleverness.
116
+ - Rewrite any candidate whose real point appears after the mobile clamp.
117
+
118
+ ## Pass, Warn, Fail
119
+
120
+ Use these rendered gates:
121
+
122
+ - `pass`: the mobile rendered preview shows the pain, proof, or curiosity by the
123
+ end of the first 3 rendered lines, and the core point is understandable
124
+ without opening "see more".
125
+ - `warn`: the mobile rendered preview creates useful curiosity but the core
126
+ point is slightly softened by wrapping, blank-line rhythm, or one missing
127
+ context word. A compact fallback is required.
128
+ - `fail`: the hook's real point appears after the first 3 mobile rendered lines,
129
+ the first rendered line is generic setup, blank lines consume the preview
130
+ before the reader sees the point, or desktop fit is the only reason it looks
131
+ good.
132
+
133
+ A draft cannot be `ready` when the selected hook has:
134
+
135
+ - no `renderedPreview`
136
+ - `mobile.seeMoreRisk: fail`
137
+ - `mobile.corePainProofOrCuriosityVisible: false`
138
+ - `mobile.corePointVisible: false`
139
+ - `pointAfterMobileClamp: true`
140
+
141
+ ## Report Format
142
+
143
+ Research reports must show rendered preview blocks for the best examples and
144
+ recommended draft directions:
145
+
146
+ ```text
147
+ Rendered preview:
148
+ mobile:
149
+ <line 1>
150
+ <line 2>
151
+ <line 3>
152
+
153
+ desktop:
154
+ <line 1>
155
+ <line 2>
156
+ <line 3>
157
+
158
+ verdict: pass | warn | fail
159
+ why: <what is visible before the fold>
160
+ ```
161
+
162
+ Do not say "it fits on mobile" without showing what the mobile reader actually
163
+ sees.
@@ -59,10 +59,19 @@ Hook research files must preserve:
59
59
  - source post URLs
60
60
  - author/profile URLs
61
61
  - engagement totals
62
+ - author follower counts when available, target follower band, follower-band
63
+ fit, engagement per 1k followers, weighted engagement per 1k followers,
64
+ reach penalty multiplier, reach-adjusted score, baseline lift when available,
65
+ why the hook carries independent of creator reach, and normalization
66
+ confidence notes
62
67
  - full-text availability
63
68
  - source hook preview measurements, including text basis, char count including
64
69
  newlines, physical/content line counts, longest nonblank line, blank-line
65
70
  visual risk, and mobile/desktop preview budget status
71
+ - rendered preview records for every kept source hook and adapted hook block,
72
+ including literal mobile/desktop preview blocks, rendered line wraps, render
73
+ basis, CSS contract version, text widths, first-screen promise visibility,
74
+ core point visibility, and whether the point lands after the mobile clamp
66
75
  - extracted hook patterns
67
76
  - selected hook basis
68
77
 
@@ -83,6 +92,9 @@ Draft files must preserve:
83
92
  - draft body
84
93
  - validation receipt, including LinkedIn preview pass/warn/fail status and
85
94
  compact fallback when the selected hook carries a warning
95
+ - rendered mobile and desktop preview blocks for the selected hook; drafts
96
+ cannot be ready when this rendered-preview audit is missing or fails mobile
97
+ visibility
86
98
  - status: `draft`, `ready`, `needs_revision`, `published`, or `archived`
87
99
 
88
100
  Multiple drafts for the same idea are expected. Keep the `ideaId` stable and
@@ -11,6 +11,7 @@ Every saved draft needs a validation receipt. A draft without this receipt is no
11
11
  - `candidateHooksConsidered`
12
12
  - `selectedHook`
13
13
  - `selectedHookWhy`
14
+ - `sourceHookReachAudit`
14
15
  - `proofClaimsUsed`
15
16
  - `proofClaimSources`
16
17
  - `storyFilesConsulted`
@@ -77,7 +78,17 @@ Each candidate should include:
77
78
  - premise tension opened
78
79
  - reader value implied
79
80
  - source pattern
81
+ - source pattern reach signals: author follower count when available, target
82
+ follower band, follower-band fit, engagement per 1k followers, weighted
83
+ engagement per 1k followers, reach-adjusted score, baseline lift, and
84
+ confidence
80
85
  - score
86
+ - `renderedPreview` using `references/linkedin-preview-rendering.md`
87
+ - literal mobile and desktop rendered preview blocks
88
+ - mobile and desktop rendered line wraps
89
+ - first-screen promise: pain, proof, or curiosity visible by the mobile clamp
90
+ - whether the core point is visible in the mobile rendered preview
91
+ - whether the point lands after the mobile clamp
81
92
  - char count including newlines and first-line / first-two-line preview measurements
82
93
  - physical line count, content line count, longest nonblank line, and blank-line risk
83
94
  - `previewBudgetStatus`: `pass`, `warn`, or `fail`
@@ -98,7 +109,7 @@ After the first draft:
98
109
  6. replace generic language with concrete words only when supported
99
110
  7. preserve the user's actual story and point
100
111
  8. remove AI tells
101
- 9. re-check LinkedIn preview fit after edits
112
+ 9. re-render the selected hook on mobile and desktop after edits
102
113
 
103
114
  ## Premise Value Audit
104
115
 
@@ -147,24 +158,96 @@ If `selectedControversy` is missing, if the audience belief is only a generic
147
158
  label like "founders want growth," or if `credibleWhyUs` depends on borrowed
148
159
  proof from another creator, save as `needs_revision`.
149
160
 
150
- ## LinkedIn Preview Audit
161
+ ## Source Hook Reach Audit
151
162
 
152
- Audit the selected hook and top candidates against conservative LinkedIn
153
- preview budgets. LinkedIn does not publish exact "see more" cutoff rules, and
154
- rendering varies by device, app version, font, media, and line break. This audit
155
- is a mobile-first safety gate, not a claim about an official LinkedIn limit.
163
+ Before a draft can be `ready`, validate that the selected source hook pattern
164
+ was chosen for a reach-normalized reason, not raw audience size.
156
165
 
157
- Use:
166
+ Record:
167
+
168
+ - `targetFollowerBand`
169
+ - `authorFollowerCount` for each source pattern when available
170
+ - `followerBandFit`: `in_target_band`, `below_target_band`,
171
+ `above_target_band`, or `unknown`
172
+ - `engagementPer1kFollowers`
173
+ - `weightedEngagementPer1kFollowers`
174
+ - `reachAdjustedScore`
175
+ - `reachPenaltyMultiplier`
176
+ - `creatorBaselineMedianEngagement` or `creator_baseline_unavailable`
177
+ - `baselineLift` or `creator_baseline_unavailable`
178
+ - `bigAccountPenaltyApplied`
179
+ - `whyTheHookCarries`
180
+ - `followerCountUnavailableSources`
181
+ - `whyThisHookIsAReachAdjustedSignal`
182
+
183
+ If the user asked for a follower range and the selected source pattern is from a
184
+ large account above the target range, the receipt must explain why it survived
185
+ the celebrity reach penalty. Use large-account hooks as secondary pattern
186
+ evidence unless they clearly overperform the creator's baseline or the hook
187
+ itself carries after reach is controlled. A draft cannot be `ready` when the
188
+ selected hook is justified only by raw total engagement or follower count.
189
+
190
+ ## LinkedIn Preview Audit
158
191
 
159
- - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
160
- - `warn`: hook is 111-140 chars including newlines, any nonblank line is 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed, but they count as physical lines.
161
- - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
192
+ Audit the selected hook and top candidates against
193
+ `references/linkedin-preview-rendering.md`. LinkedIn does not publish exact
194
+ "see more" cutoff rules, and rendering varies by device, app version, font,
195
+ media, and line break. This audit is a mobile-first rendered-preview safety
196
+ gate, not a claim about an official LinkedIn limit.
197
+
198
+ Character budgets are secondary diagnostics. They cannot be the only evidence
199
+ that a hook works. Every selected hook must include literal rendered mobile and
200
+ desktop preview blocks.
201
+
202
+ Default deterministic renderer when no authenticated LinkedIn screenshot is
203
+ available:
204
+
205
+ ```text
206
+ cssContractVersion: linkedin-preview-rendering/v1
207
+ font size: 14px
208
+ line height: 21px
209
+ white space: pre-wrap
210
+ overflow wrap: break-word
211
+ mobile text width: 308px
212
+ desktop text width: 582px
213
+ review clamp: first 3 rendered text lines
214
+ ```
215
+
216
+ Use rendered gates:
217
+
218
+ - `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
219
+ the end of the first 3 rendered lines, and the core point is understandable
220
+ without opening "see more".
221
+ - `warn`: the mobile rendered preview creates useful curiosity but the core
222
+ point is slightly softened by wrapping, blank-line rhythm, or one missing
223
+ context word. A compact fallback is required.
224
+ - `fail`: the hook's real point appears after the first 3 mobile rendered
225
+ lines, the first rendered line is generic setup, blank lines consume the
226
+ preview before the reader sees the point, or desktop fit is the only reason it
227
+ looks good.
162
228
 
163
229
  Desktop preview usually has more room. Still record desktop fit, but never let
164
230
  desktop fit compensate for a mobile `fail`.
165
231
 
166
232
  Record:
167
233
 
234
+ - `renderedPreview`
235
+ - `renderBasis`: `linkedin_css_contract`, `authenticated_linkedin_screenshot`,
236
+ or `manual_user_source`
237
+ - `cssContractVersion`
238
+ - `mobileRenderedPreviewBlock`
239
+ - `desktopRenderedPreviewBlock`
240
+ - `mobileRenderedLines`
241
+ - `desktopRenderedLines`
242
+ - `mobileTextWidthPx`
243
+ - `desktopTextWidthPx`
244
+ - `mobileRenderedLineCount`
245
+ - `desktopRenderedLineCount`
246
+ - `corePainProofOrCuriosityVisibleMobile`
247
+ - `corePainProofOrCuriosityVisibleDesktop`
248
+ - `corePointVisibleMobile`
249
+ - `corePointVisibleDesktop`
250
+ - `pointAfterMobileClamp`
168
251
  - `charCount`
169
252
  - `charCountIncludingNewlines`
170
253
  - `firstLineChars`
@@ -183,10 +266,14 @@ Record:
183
266
  - `rewriteIfTruncated`
184
267
  - `compactFallback` when `previewBudgetStatus` is `warn`
185
268
 
186
- If the hook only works after likely truncation, rewrite it. A draft cannot be
187
- `ready` with `previewBudgetStatus: fail`. A draft may be `ready` with
188
- `previewBudgetStatus: warn` only when the warning is explicit, usually because
189
- the user prefers blank-line rhythm, and the receipt includes a compact fallback.
269
+ If the hook only works after the rendered mobile clamp, rewrite it. A draft
270
+ cannot be `ready` with `previewBudgetStatus: fail`,
271
+ `mobilePreviewFit: fail`, missing `renderedPreview`,
272
+ `corePainProofOrCuriosityVisibleMobile: false`,
273
+ `corePointVisibleMobile: false`, or `pointAfterMobileClamp: true`. A draft may
274
+ be `ready` with `previewBudgetStatus: warn` only when the warning is explicit,
275
+ usually because the user prefers blank-line rhythm, and the receipt includes a
276
+ compact fallback.
190
277
 
191
278
  ## Simplifier / Concrete-Language Audit
192
279