Repository navigation
#367 feat: notification composer, preview and test-send (epic #359) - #456
Merged
Merged
Conversation
…e origin
- src/core/notifications/diff.py: Watcher 3ba20be, unchanged but the
ContentOptions import, with its tests and the byte-exact WSLCB fixtures
(excluded from the whitespace hooks).
- Operator Q1 (2026-10-30): the base is the origin, and an item's page is
/dashboard/info-items/{id} under it. The API built {base}/info-items/{id},
a 404 under any origin. src/core/public_base_url.py now serves the API and
the composer; a base that is not an origin yields no link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ield by field - content.py: an event (co-core's model as is), ItemFacts and one row's ContentOptions in; a title and a body out. Watcher's layout, toggles and diff caps, but the context is built field by field (D1), every render is sandboxed and nothing returns template source (D2). compose() is the send path: a failing custom template renders the event's default, with a WARNING naming the subscription and the error type. - default_templates.py: Watcher's text word for word (shadow parity), with Archiver's names and item link; TEMPLATE_VARIABLES is pinned equal to the context. - preview_fixtures.py: one co-core event per kind, an item, the canned texts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s facts - diff_loader.py (D4): the URI comes on the event. Processor bucket only, size-checked then a ranged read, hash-checked against the fingerprint, off the loop, never raises. The client is built from the processor-text-reader credential explicitly, never ambient; no key degrades every diff to unavailable. - item_facts.py (D1): name/description from the item, url/domain from the announcing source, the resolved cadence; tags inert until #362. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Notifier.dispatch_composed sends title_template='{{ title }}',
body_template='{{ body }}' and the text as variables, so Notifier never
evaluates composed text as Jinja. One attempt; every failure is
NotifierUnavailable without the key. The boundary test now allows dispatch
in that one function and pins its shape; send_test, redeliver and the
channel writes stay banned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- D5: each content block gets its Jinja title and body; POST /preview renders the posted form strict in the sandbox against the fixtures, no Notifier call. Preview and save share ContentOptions' validator; a syntax error names its block. #363's carry-over and override refusal go: the form is the whole truth. - D6: POST /{id}/test-send, edit page only, static confirm naming the channel: the row's saved options against the canned change, [Test] title, one dispatch with archiver:test:{id}:{ULID} and test metadata; the dispatch id or the reason as a flash; one INFO line of ids. - Docs: NOTIFICATION_COMPOSER.md (new), NOTIFICATIONS.md, PAGES.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… D4 proof CLI - archiver.service: ARCHIVER_PROCESSOR_TEXT_BUCKET=co-gcs-processor, the processor-text-reader credential WITH the empty SetCredential fallback (degrade, don't refuse), and ARCHIVER_PUBLIC_BASE_URL as its own line. Pinned to this unit alone; no ambient GOOGLE_APPLICATION_CREDENTIALS. - The lifespan builds one reader after the gates, makes no request, warns once when a bucket has no usable key, closes it at shutdown, and says where links point. - check_processor_text: one read through the service's reader; prints ok <bytes> or the reason, never the text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the reader The variables table sits in a labelled, focusable .table-scroll. The lifespan's reader announcement moves into diff_loader, where the bucket variable is a local constant the outbound-env audit can resolve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ariables (D7) - New entries: the Notifier gate's three variables and the dev pair (#368's held entry), ARCHIVER_PROCESSOR_TEXT_BUCKET, ARCHIVER_DIFF_MAX_INPUT_BYTES. - ARCHIVER_PUBLIC_BASE_URL rewritten: the origin, /dashboard/info-items, the unit's own line. - Room made by relocating four broker-side rationale paragraphs verbatim to BUS_CLIENT.md § The broker boundary (rules stay inline); 9981 -> 9619 exact tokens. prove-no-loss: 0 lost; seams acknowledged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e loop SandboxedEnvironment stops escapes and caps range, but 'x' * 10**9 still allocated a gigabyte in the one process. Intercept * (100 000 items max) and ** (exponent 100 max) on every render, and run the preview's render and the test-send's compose in a worker thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…page src/api/routes/ changed, so the changelog trigger applies. Service-only bump; the snapshot carries the version, SDK surface unchanged (5.16.0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #367 (epic #359).
Notification composer, live preview and dashboard-only test-send, built to the coordinator brief's D1–D7 (issuecomment-6529430076). The implementer review is issuecomment-6529775505; the report and post-merge host steps are issuecomment-6531358718.
src/core/notifications/): ported from Watcher3ba20be, with the default text word for word.TEMPLATE_VARIABLES(D1).*/**, and never returns template source (D2).gs://co-gcs-processoronly, through theprocessor-text-readerunit credential with aSetCredential=fallback. Each read is size-capped and hash-checked, and the loader never raises.Notifier.dispatch_composed(D3): the adapter's one dispatch. It sends the fixed templates{{ title }}/{{ body }}, with the composed text as variables. An AST boundary test and a respx test pin it.ARCHIVER_PUBLIC_BASE_URLis an origin (Q1).dashboard_url→/dashboard/info-items/{id}. v4.40.0 changelog[service]; SDK unchanged.deploy/archiver.service: sets the bucket and the base URL, and loads the key withLoadCredential=plus theSetCredential=fallback. NoGOOGLE_APPLICATION_CREDENTIALS.Verification:
dev-sink(dispatch01M672JR9QV071539XKXKGPC4D, succeeded). Nothing was sent on 8000.CR --fix: 3 fixes, 3 observations.Host step 1 is done:
.envbase-URL line is removed;root:root 600;🤖 Generated with Claude Code