Skip to content

Commit dfa63a7

Browse files
committed
feat(security,portal): enforce email/HMAC portal gate, block private GROQ in MCP/AJ chat, and add 2-way Run-of-Show co-editor
1 parent d18b08e commit dfa63a7

19 files changed

Lines changed: 1828 additions & 173 deletions

File tree

‎apps/ops/src/App.tsx‎

Lines changed: 336 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ interface SponsorLeadDoc {
119119
intent?: string;
120120
threadId?: string;
121121
notes?: string;
122+
preCallNotes?: string;
123+
runOfShowNotes?: string;
124+
guestQuestions?: string;
125+
runOfShowUpdatedAt?: string;
126+
runOfShowUpdatedBy?: string;
127+
legacyPortalToken?: string;
122128
deliverables?: DeliverableItem[];
123129
sharedAssets?: SharedAssetItem[];
124130
}
@@ -141,6 +147,11 @@ interface BookingDoc {
141147
preMeetingGoogleEventId?: string;
142148
preMeetingMeetUrl?: string;
143149
topics?: string;
150+
preCallNotes?: string;
151+
runOfShowNotes?: string;
152+
guestQuestions?: string;
153+
runOfShowUpdatedAt?: string;
154+
runOfShowUpdatedBy?: string;
144155
appointmentScheduleUrl?: string;
145156
googleCalendarEventId?: string;
146157
passionfrootCollaborationId?: string;
@@ -150,6 +161,7 @@ interface BookingDoc {
150161
signatureData?: string;
151162
signedAt?: string;
152163
portalToken?: string;
164+
legacyPortalToken?: string;
153165
sharedAssets?: SharedAssetItem[];
154166
}
155167

@@ -338,6 +350,16 @@ export function App() {
338350
const [vaultUrlInput, setVaultUrlInput] = useState<Record<string, string>>(
339351
{},
340352
);
353+
const [rosDrafts, setRosDrafts] = useState<
354+
Record<
355+
string,
356+
{
357+
preCallNotes: string;
358+
runOfShowNotes: string;
359+
guestQuestions: string;
360+
}
361+
>
362+
>({});
341363
const [busyActionId, setBusyActionId] = useState<string | null>(null);
342364

343365
const showFlash = useCallback((msg: string) => {
@@ -527,6 +549,39 @@ export function App() {
527549
}
528550
};
529551

552+
const handleSaveRunOfShow = async (
553+
docId: string,
554+
values: {
555+
preCallNotes: string;
556+
runOfShowNotes: string;
557+
guestQuestions: string;
558+
},
559+
) => {
560+
setBusyActionId(`${docId}:ros`);
561+
try {
562+
const nowIso = new Date().toISOString();
563+
const patchData = {
564+
preCallNotes: values.preCallNotes,
565+
runOfShowNotes: values.runOfShowNotes,
566+
guestQuestions: values.guestQuestions,
567+
runOfShowUpdatedAt: nowIso,
568+
runOfShowUpdatedBy: "Alex Patterson (Ops App)",
569+
};
570+
await patchDocWithFallback(docId, patchData);
571+
setBookings((prev) =>
572+
prev.map((b) => (b._id === docId ? { ...b, ...patchData } : b)),
573+
);
574+
setLeads((prev) =>
575+
prev.map((l) => (l._id === docId ? { ...l, ...patchData } : l)),
576+
);
577+
showFlash("✓ Saved Run-of-Show & Pre-Call Notes to Sanity (live on Guest Portal)");
578+
} catch (err: any) {
579+
showFlash(`Error saving Run-of-Show: ${err?.message || "Unknown error"}`);
580+
} finally {
581+
setBusyActionId(null);
582+
}
583+
};
584+
530585
const handleCreateLead = async (e: React.FormEvent) => {
531586
e.preventDefault();
532587
if (!newCompany.trim() || !newContactEmail.trim()) return;
@@ -3349,10 +3404,10 @@ export function App() {
33493404
}
33503405
style={primaryBtnStyle}
33513406
>
3352-
{isExpanded
3353-
? "Hide Details"
3354-
: `Topics, E-SIGN & Vault (${assetCount})`}
3355-
</button>
3407+
{isExpanded
3408+
? "Hide Details"
3409+
: `Run-of-Show, E-SIGN & Vault (${assetCount})`}
3410+
</button>
33563411
<a
33573412
href={vaultUrl}
33583413
target="_blank"
@@ -3691,6 +3746,283 @@ export function App() {
36913746
</div>
36923747
</div>
36933748
</div>
3749+
3750+
{/* Shared 2-Way Run-of-Show & Pre-Call Notes Co-Editor */}
3751+
{(() => {
3752+
const currentRos = rosDrafts[b._id] || {
3753+
preCallNotes: b.preCallNotes || "",
3754+
runOfShowNotes: b.runOfShowNotes || "",
3755+
guestQuestions: b.guestQuestions || "",
3756+
};
3757+
return (
3758+
<div
3759+
style={{
3760+
marginTop: 16,
3761+
padding: 16,
3762+
borderRadius: 10,
3763+
background: "#fff",
3764+
border: "2px solid #c4b5fd",
3765+
}}
3766+
>
3767+
<div
3768+
style={{
3769+
display: "flex",
3770+
justifyContent: "space-between",
3771+
alignItems: "center",
3772+
flexWrap: "wrap",
3773+
gap: 8,
3774+
marginBottom: 12,
3775+
}}
3776+
>
3777+
<div>
3778+
<h4
3779+
style={{
3780+
margin: 0,
3781+
fontSize: 14,
3782+
color: "#4B2E83",
3783+
}}
3784+
>
3785+
📝 2-Way Run-of-Show &amp; Pre-Call
3786+
Coordination Notes (Syncs Live with{" "}
3787+
<code>{portalUrl}#run-of-show</code>)
3788+
</h4>
3789+
<div
3790+
style={{
3791+
fontSize: 11,
3792+
color: "#6b7280",
3793+
marginTop: 2,
3794+
}}
3795+
>
3796+
Both you and{" "}
3797+
<strong>{b.guestName}</strong> can
3798+
edit these notes before and during the
3799+
session.
3800+
{b.runOfShowUpdatedAt
3801+
? ` Last saved ${formatDateTime(b.runOfShowUpdatedAt)} by ${b.runOfShowUpdatedBy || "Host"}.`
3802+
: ""}
3803+
</div>
3804+
</div>
3805+
<div
3806+
style={{
3807+
display: "flex",
3808+
gap: 6,
3809+
flexWrap: "wrap",
3810+
}}
3811+
>
3812+
<button
3813+
type="button"
3814+
onClick={() =>
3815+
setRosDrafts((prev) => ({
3816+
...prev,
3817+
[b._id]: {
3818+
preCallNotes:
3819+
currentRos.preCallNotes ||
3820+
[
3821+
`• Target Audience Takeaway: Practical mental model + live demo of ${b.company || b.guestName}`,
3822+
`• Screen-Share Check: 1080p editor/browser zoomed to 125%-150%`,
3823+
`• Audio/Video Check: Headphones on (no echo in StreamYard)`,
3824+
].join("\n"),
3825+
guestQuestions:
3826+
currentRos.guestQuestions ||
3827+
[
3828+
`• Must-Ask #1: What developer pain point sparked ${b.company || "this project"}?`,
3829+
`• Must-Ask #2: Walk us through the architecture & live code`,
3830+
`• Lower-Third Links: ${b.socialUrl || "https://codingcat.dev"}`,
3831+
].join("\n"),
3832+
runOfShowNotes: [
3833+
`00:00 - 03:00 | 1. Cold Open & Origin Story`,
3834+
` • Welcome ${b.guestName} (${b.company || "Guest"}) to CodingCat.dev`,
3835+
` • The 30-second elevator pitch: What problem were you tired of hitting in production?`,
3836+
``,
3837+
`03:00 - 12:00 | 2. Architecture Mental Model`,
3838+
` • How developers used to wire this up vs. how ${b.company || b.guestName} approaches it`,
3839+
` • Under-the-hood primitives, state flow, and developer ergonomics`,
3840+
``,
3841+
`12:00 - 28:00 | 3. Show Me the Code (Live Screen-Share / Demo)`,
3842+
` • Step-by-step walkthrough in real code (zero slideware)`,
3843+
` • Key workflows, CLI/UI ergonomics, and live inspection`,
3844+
``,
3845+
`28:00 - 38:00 | 4. Production Trade-Offs & Sharp Edges`,
3846+
` • Where this shines vs. when teams should use something else`,
3847+
` • Security, performance, and scaling gotchas`,
3848+
``,
3849+
`38:00 - 45:00 | 5. Verdict, Roadmap & Where to Try It`,
3850+
` • What is shipping next on the roadmap`,
3851+
` • Links to docs, GitHub repo, and starter templates`,
3852+
].join("\n"),
3853+
},
3854+
}))
3855+
}
3856+
style={btnStyle}
3857+
>
3858+
Load 5-Part Episode Template
3859+
</button>
3860+
<button
3861+
type="button"
3862+
onClick={() =>
3863+
handleCopy(
3864+
`${portalUrl}#run-of-show`,
3865+
`ros-link-${b._id}`,
3866+
)
3867+
}
3868+
style={btnStyle}
3869+
>
3870+
{copiedId === `ros-link-${b._id}`
3871+
? "✓ Copied #run-of-show URL"
3872+
: "Copy #run-of-show URL"}
3873+
</button>
3874+
<button
3875+
type="button"
3876+
disabled={
3877+
busyActionId === `${b._id}:ros`
3878+
}
3879+
onClick={() =>
3880+
handleSaveRunOfShow(
3881+
b._id,
3882+
currentRos,
3883+
)
3884+
}
3885+
style={primaryBtnStyle}
3886+
>
3887+
{busyActionId === `${b._id}:ros`
3888+
? "Saving..."
3889+
: "💾 Save Run-of-Show to Sanity"}
3890+
</button>
3891+
</div>
3892+
</div>
3893+
3894+
<div
3895+
style={{
3896+
display: "grid",
3897+
gridTemplateColumns:
3898+
"repeat(auto-fit, minmax(280px, 1fr))",
3899+
gap: 12,
3900+
}}
3901+
>
3902+
<div
3903+
style={{
3904+
display: "flex",
3905+
flexDirection: "column",
3906+
gap: 8,
3907+
}}
3908+
>
3909+
<div>
3910+
<label
3911+
style={{
3912+
display: "block",
3913+
fontSize: 11,
3914+
fontWeight: 700,
3915+
color: "#374151",
3916+
marginBottom: 4,
3917+
}}
3918+
>
3919+
1. 15m Pre-Call Alignment &amp; Tech
3920+
Check Notes:
3921+
</label>
3922+
<textarea
3923+
rows={5}
3924+
value={currentRos.preCallNotes}
3925+
onChange={(e) =>
3926+
setRosDrafts((prev) => ({
3927+
...prev,
3928+
[b._id]: {
3929+
...currentRos,
3930+
preCallNotes: e.target.value,
3931+
},
3932+
}))
3933+
}
3934+
placeholder="Pre-call takeaways, screen-share setup, bio/pronunciation notes..."
3935+
style={{
3936+
width: "100%",
3937+
padding: 8,
3938+
borderRadius: 6,
3939+
border: "1px solid #d1d5db",
3940+
fontFamily: "monospace",
3941+
fontSize: 12,
3942+
lineHeight: 1.4,
3943+
}}
3944+
/>
3945+
</div>
3946+
<div>
3947+
<label
3948+
style={{
3949+
display: "block",
3950+
fontSize: 11,
3951+
fontWeight: 700,
3952+
color: "#374151",
3953+
marginBottom: 4,
3954+
}}
3955+
>
3956+
2. Guest Prompts, Links &amp;
3957+
Lower-Third Callouts:
3958+
</label>
3959+
<textarea
3960+
rows={4}
3961+
value={currentRos.guestQuestions}
3962+
onChange={(e) =>
3963+
setRosDrafts((prev) => ({
3964+
...prev,
3965+
[b._id]: {
3966+
...currentRos,
3967+
guestQuestions:
3968+
e.target.value,
3969+
},
3970+
}))
3971+
}
3972+
placeholder="Must-ask questions, GitHub repo URL, docs links..."
3973+
style={{
3974+
width: "100%",
3975+
padding: 8,
3976+
borderRadius: 6,
3977+
border: "1px solid #d1d5db",
3978+
fontFamily: "monospace",
3979+
fontSize: 12,
3980+
lineHeight: 1.4,
3981+
}}
3982+
/>
3983+
</div>
3984+
</div>
3985+
<div>
3986+
<label
3987+
style={{
3988+
display: "block",
3989+
fontSize: 11,
3990+
fontWeight: 700,
3991+
color: "#374151",
3992+
marginBottom: 4,
3993+
}}
3994+
>
3995+
3. 45-Minute Episode Run-of-Show
3996+
Outline (5-Part Arc):
3997+
</label>
3998+
<textarea
3999+
rows={11}
4000+
value={currentRos.runOfShowNotes}
4001+
onChange={(e) =>
4002+
setRosDrafts((prev) => ({
4003+
...prev,
4004+
[b._id]: {
4005+
...currentRos,
4006+
runOfShowNotes: e.target.value,
4007+
},
4008+
}))
4009+
}
4010+
placeholder="00:00 - 03:00 | 1. Cold Open & Origin Story..."
4011+
style={{
4012+
width: "100%",
4013+
padding: 8,
4014+
borderRadius: 6,
4015+
border: "1px solid #d1d5db",
4016+
fontFamily: "monospace",
4017+
fontSize: 12,
4018+
lineHeight: 1.4,
4019+
}}
4020+
/>
4021+
</div>
4022+
</div>
4023+
</div>
4024+
);
4025+
})()}
36944026
</td>
36954027
</tr>
36964028
)}

0 commit comments

Comments
 (0)