Skip to content

fix(plugin-forms): give the digest cron task a valid name - #3979

Merged
ascorbic merged 2 commits into
emdash-cms:mainfrom
eisenbruch:fix/forms-digest-task-name
Oct 9, 2026
Merged

ascorbic merged 2 commits into
emdash-cms:mainfrom
eisenbruch:fix/forms-digest-task-name

Conversation

@eisenbruch

@eisenbruch eisenbruch commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Makes the forms plugin's daily digest schedulable.

The plugin named its cron task digest:<formId>. ctx.cron only accepts letters, numbers, dashes and underscores (validateTaskName in packages/core/src/plugins/cron.ts), so every schedule and cancel call for a digest threw. Creating a form with digestEnabled: true, or turning it on later, answered 500. On create the form had already been written, so it was left stored with the digest on and no task: it sent no per-submission email and no digest.

The task is now named digest-<formId>. The name is built and read back in one place, src/digest-task.ts, used by the create, update, duplicate and delete handlers, the digest handler and the cron dispatcher. Duplicating a form copied its digestEnabled setting but never scheduled a task for the copy; it now does.

No task named digest:<formId> can exist in a database, because scheduling one always threw, so nothing needs migrating. A form that was already saved with the digest on by a failed create still has no task; turning its digest off and on again schedules it.

Closes #3975

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5.5 (Claude Code)

Screenshots / test output

Not applicable (no UI change).

tests/digest-task.test.ts runs the create, update and duplicate handlers against a cron that refuses the names the real one refuses. Before the fix the create and update tests failed with Invalid task name "digest:01M4C0YKWPN6VTW8JY5PA9WGZV", and the duplicate test failed with no task scheduled; with it:

packages/plugins/forms
 Test Files  10 passed (10)
      Tests  57 passed (57)

Also run against the real cron on demos/simple (SQLite) with formsPlugin() added, reading _emdash_cron_tasks after each call:

forms/create  digestEnabled: true, digestHour: 7   -> 200, digest-01M4C12ADEJKKHKMPBXNQZCQM5 | 0 7 * * *
forms/update  digestHour: 18                       -> 200, digest-01M4C12ADEJKKHKMPBXNQZCQM5 | 0 18 * * *
forms/update  digestEnabled: false                 -> 200, no digest task left

@changeset-bot

changeset-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 094581c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@emdash-cms/plugin-forms Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@3979

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@3979

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@3979

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@3979

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@3979

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@3979

emdash

npm i https://pkg.pr.new/emdash@3979

create-emdash

npm i https://pkg.pr.new/create-emdash@3979

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@3979

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@3979

@emdash-cms/plugin-test

npm i https://pkg.pr.new/@emdash-cms/plugin-test@3979

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@3979

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@3979

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@3979

@emdash-cms/registry-loader

npm i https://pkg.pr.new/@emdash-cms/registry-loader@3979

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@3979

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@3979

upgrade-emdash

npm i https://pkg.pr.new/upgrade-emdash@3979

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@3979

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@3979

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@3979

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@3979

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@3979

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@3979

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@3979

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@3979

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@3979

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@3979

commit: 094581c

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@eisenbruch
eisenbruch marked this pull request as ready for review October 8, 2026 17:11

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR fixes the right bug in the right way. The digest:<formId> cron task name was always invalid under validateTaskName (letters, numbers, dashes, underscores only), so every schedule/cancel threw and forms saved with digest enabled had no actual task. Centralizing the name in src/digest-task.ts and switching to digest-<formId> eliminates that, and the new regression test exercises the create/update handlers against a cron stub that rejects the names the real cron rejects.

I verified:

  • The new task name matches the allowed pattern and form IDs (ULIDs) contain only valid characters.
  • All schedules, cancels, and the cron dispatcher route through digestTaskName/formIdFromDigestTask consistently.
  • No migration is needed: an invalid digest:... name could never be inserted because CronAccessImpl.schedule validates before writing.
  • Core cron scopes events by plugin_id, so the else { formIdFromDigestTask(...) } dispatch only ever sees this plugin's own tasks.
  • The changeset for @emdash-cms/plugin-forms is technically accurate and user-facing.

The changed code is clean. One related gap remains: formsDuplicateHandler copies settings.digestEnabled but never schedules a digest for the new form, so duplicating a form with digest on leaves it in the same broken state this PR fixes for creates. That is pre-existing, but worth addressing while the digest scheduling logic is being centralized.


Findings

  • [suggestion] packages/plugins/forms/src/handlers/forms.ts:221

    Duplicating a form copies the source's digestEnabled setting, but formsDuplicateHandler never schedules a digest cron for the new form. A duplicate created from a form with digest on ends up with digestEnabled: true and no scheduled task—the same broken state this PR fixes for creates. After forms(ctx).put(id, duplicate), also schedule if duplicate.settings.digestEnabled is true.

    	await forms(ctx).put(id, duplicate);
    
    	if (duplicate.settings.digestEnabled && ctx.cron) {
    		await ctx.cron.schedule(digestTaskName(id), {
    			schedule: `0 ${duplicate.settings.digestHour} * * *`,
    		});
    	}
    

@emdashbot emdashbot Bot added the review/awaiting-author Reviewed; waiting on the author to respond label Oct 8, 2026
@eisenbruch

Copy link
Copy Markdown
Contributor Author

Taken the duplicate finding in 094581c: formsDuplicateHandler now schedules digest-<newId> when the copy has the digest on, with a regression test that fails without it. Changeset and description updated.

@github-actions github-actions Bot added review/needs-rereview New commits since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Oct 8, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@emdashbot emdashbot Bot added review/approved Approved; no new commits since and removed review/needs-rereview New commits since the last review labels Oct 9, 2026
@ascorbic
ascorbic merged commit 65aa1f4 into emdash-cms:main Oct 9, 2026
34 checks passed
@emdashbot emdashbot Bot mentioned this pull request Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-forms: enabling the daily digest fails with "Invalid task name" and leaves the form sending nothing

2 participants