Implementation Plan: Scheduled Publishing for Journal
Overview
Implement scheduled publishing for Captain's Log (plugins/journal/), enabling users to schedule unpublished notes for publication at a future date/time. At the scheduled time, a one-time job executed by the Job app broadcasts the pre-signed Kind 1 event to Nostr relays and updates the entry status.
Architecture & Signing Decision
- Author Identity: Journal notes are authored by the master user (
BOT_MASTER_PUBKEY). - Signing Mechanism: Browser pre-signing at schedule time (
nostr.signEventclientAction). The active browser signer signs the Kind 1 event when the user clicks Accept in the Schedule modal. - Server Execution: The one-time job created via
SchedulerV2.operations.createruns with task typeplugin-tool(alias: 'journal',toolName: 'publish_scheduled',input: { id: entryId }). At the scheduled time,executeToolbroadcasts the pre-signed event using the shared Nostr pool without requiring browser presence or server-side private keys. - Contract Boundary: All Journal scheduling interactions use
PluginContext.capabilitiesand theSchedulerV2contract. No direct Job database access or Job implementation imports.
Implementation Checklist
1. Schema & Types
- Add
'date'and'time'toinputTypeenum insrc/web/ui-schema.ts. - Update
JournalEntryStatusinplugins/journal/db.tsto'private' | 'scheduled' | 'published'. - Update
JournalStatusSchemainplugins/journal/ai.tsto include'scheduled'. - Add
publish_scheduledtool call schema toplugins/journal/ai.ts.
- Add
2. Web Client Signing Action (
nostr.signEvent)- Implement generic
handleNostrSignEventActioninweb/src/nostr/signEventAction.ts. - Register
nostr.signEventinweb/src/commands/useCommands.ts. - Support future time validation in client action before requesting signature.
- Implement generic
3. Journal Web UI (Schedule Modal & Entry Meta)
- Keep existing Publish button unchanged.
- Add Schedule button beside Publish in
plugins/journal/commands/today/component.tsfor unpublished entries. - Render compact Schedule modal (
schedule-formsubcommand) with date input, time input, timezone label, preview, and Accept / Cancel buttons. - For scheduled entries, render
scheduled ℹstatus badge with overflow menu (Reschedule, Cancel schedule, Publish now). - On edit of an already-scheduled entry, display the schedule modal prefilled with existing date/time and rescheduling notification.
4. Journal Adapter & Commands (
plugins/journal/adapter.ts)- Implement
schedule-formsubcommand to render the schedule modal. - Form submission requests browser signature via
nostr.signEvent. - Implement
schedule-confirmsubcommand: validates signed event, checks for duplicates, invokesSchedulerV2.operations.create, updates entry to'scheduled', storesjobResourceIdandsignedEventin metadata. - Implement
schedule-cancelsubcommand: cancels scheduled job and resets entry status to'private'. - Handle deletion of scheduled entries: cancel scheduled job if entry is deleted.
- Implement
5. Scheduled Execution Tool (
plugins/journal/ai.ts)- Implement
publish_scheduledinexecuteTool:- Checks entry exists and is still in
'scheduled'status (aborts cleanly if deleted/unscheduled). - Broadcasts pre-signed event to author write relays using
fetchNip65WriteRelaysandpublishSignedEventToRelays. - Handles relay rejection (reports failure, preserves scheduled state, does not mark published).
- On relay success: constructs
nostrUrl(nostr://nevent...), updates entry status to'published', and saves relay outcomes to metadata.
- Checks entry exists and is still in
- Implement
6. Verification & Quality
- Scoped TypeScript typechecks (
bunx tsc --noEmiton modified targets). - Targeted ESLint with
--fixon all modified files. - Verify zero modification of real journal entries, jobs, or Nostr relays during static verification.
- Verify
restart.requestedis NOT created or modified.
- Scoped TypeScript typechecks (