Commit Graph

602 Commits

Author SHA1 Message Date
9b7878a623 feat(navigation): bind binders accept (realtime, realtimeRegistry) params
Extend bindProductionNavigation and bindDevSeedNavigation to 7-arg signatures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:41:16 +02:00
579c5911fd feat(marketing-pages): bind binders accept (realtime, realtimeRegistry) params
Extend bindProductionMarketingPages and bindDevSeedMarketingPages to 7-arg signatures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:41:13 +02:00
f85b0891ee feat(media): bind binders accept (realtime, realtimeRegistry) params
Some checks failed
CI / typecheck + lint + boundaries + test + build (push) Has been cancelled
CI / Playwright e2e (push) Has been cancelled
CI / Storybook smoke tests (push) Has been cancelled
Sentry PII guard (R31) / pii-guard (push) Has been cancelled
Extend bindProductionMedia and bindDevSeedMedia to 7-arg signatures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:41:10 +02:00
1f12e6af12 feat(blog): bind binders accept (realtime, realtimeRegistry) params
Extend bindProductionBlog and bindDevSeedBlog to 7-arg signatures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:41:07 +02:00
7e1d2d5c4b feat(auth): bind binders accept (realtime, realtimeRegistry) params
Extend bindProductionAuth and bindDevSeedAuth to 7-arg signatures.
Fix RecordingRealtimeBroadcaster scope type to structurally match ChannelScope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:41:04 +02:00
71c1a6c216 chore(generators): add // <gen:realtime-*> anchors to feature template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:07:43 +02:00
c882614153 test(core-eslint): extend anchor-presence guard for realtime anchors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:07:21 +02:00
f5b0e8e6ae chore(navigation): add // <gen:realtime-*> anchor comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:07:08 +02:00
2939b94302 chore(marketing-pages): add // <gen:realtime-*> anchor comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:06:45 +02:00
4fd95921f0 chore(media): add // <gen:realtime-*> anchor comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:06:14 +02:00
8367cb184e chore(blog): add // <gen:realtime-*> anchor comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:05:51 +02:00
03a0900688 chore(auth): add // <gen:realtime-*> anchor comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:05:27 +02:00
122a81b2ca feat(core-eslint): rule no-realtime-handler-reexport
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 22:01:03 +02:00
77467de702 feat(core-eslint): rule no-direct-socket-io
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:59:52 +02:00
928357f221 feat(core-testing): RecordingRealtimeBroadcaster (local-type-alias pattern) 2026-05-08 21:56:18 +02:00
228cfb57c0 fix(core-realtime): address Phase 2 code review (typing, error handling, params)
- Type socket.data.user via AppSocketData/AppSocket generics (no more any)
- Wrap Gate 1 authenticator call in try/catch so exceptions reject the
  connection with connect_error instead of being swallowed
- Fix Gate 3 userScoped channel authorize: derive params.userId from the
  authenticated socket user so owner-inbound is accepted
- Await io.close(callback) in stop() to ensure full shutdown
- Remove unused httpServer field from constructor (io already holds the ref)
- Extract CHANNEL_ROOM_PREFIX/channelRoom helper to channel-room.ts;
  replace three `ch:${name}` magic strings; re-export from index
- Add JSDoc to ChannelScope explaining userScoped params/template convention
- Fix "allows subscribe + invokes handler with ctx" test: hoist received
  outside beforeEach and assert ctx shape
- New test: rejects connection when authenticator throws
- New test: userScoped channel accepts inbound from owner, rejects from anon
2026-05-08 21:52:52 +02:00
964fabf796 fix(core-realtime): remove unused PLACEHOLDER var and ClientSocket import (lint) 2026-05-08 21:16:44 +02:00
d4a3f95938 feat(core-realtime): public barrel 2026-05-08 21:16:22 +02:00
2939ff8bb5 feat(core-realtime): SocketIORealtimeServer (4 lifecycle gates) 2026-05-08 21:16:03 +02:00
9d04bdc65b feat(core-realtime): SocketIORealtimeBroadcaster 2026-05-08 21:15:07 +02:00
86b9418758 feat(core-realtime): IRealtimeServer interface 2026-05-08 21:14:43 +02:00
5e57c66140 feat(core-realtime): InMemoryRealtimeBroadcaster (test/dev impl) 2026-05-08 21:14:33 +02:00
6c5484b874 feat(core-realtime): RealtimeHandlerRegistry 2026-05-08 21:14:11 +02:00
f072435024 feat(core-realtime): authorize function (4 scope kinds) 2026-05-08 21:13:48 +02:00
0771601571 feat(core-realtime): channel-template matcher (placeholder extraction) 2026-05-08 21:13:18 +02:00
691b4942d5 feat(core-realtime): IRealtimeAuthenticator interface 2026-05-08 21:12:56 +02:00
caf86abbe5 feat(core-realtime): IRealtimeHandler + IInboundDescriptor types 2026-05-08 21:12:47 +02:00
795511b5b3 feat(core-realtime): IRealtimeBroadcaster interface + symbol registry 2026-05-08 21:12:36 +02:00
7e922bc406 feat(core-realtime): RealtimeChannelDescriptor + defineRealtimeChannel 2026-05-08 21:12:24 +02:00
1a0e3c20ff chore(core-realtime): align scaffold style with core-events (review polish) 2026-05-08 21:09:50 +02:00
fd76aa445d chore(core-eslint,turbo): tag @repo/core-realtime as core
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:59:01 +02:00
bbdb456aa0 chore(core-realtime): scaffold package
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 20:58:05 +02:00
82002a0ee8 docs(plan): realtime layer implementation plan (45 tasks, 11 phases) 2026-05-08 20:31:26 +02:00
500d835f0e docs(spec): realtime layer (Socket.IO) design
Long-form design for the bidirectional realtime layer the user
brainstormed on 2026-05-08. Builds on ADR-015 (events and jobs);
hybrid pattern where IRealtimeBroadcaster is layered alongside
IEventBus, with a bridge for forwarding allowlisted bus events to
realtime channels.

v1 ships: @repo/core-realtime package, custom Node server in
apps/web-next, cookie-session auth, four scope kinds, two
generators (gen realtime channel|handler), three new anchors per
feature, RecordingRealtimeBroadcaster test helper, realtime-ping
proof-of-life. Dashboard, DB-backed roles/permissions, multi-instance
fanout, and a generic core-package generator are deferred — captured
in §13 / §14 / §15.

Companion HTML explainer at realtime-auth-explainer.html (project root,
uncommitted) walks the four-checkpoint auth model visually. ADR-016
will be created during implementation.
2026-05-08 19:51:06 +02:00
69c445de26 docs: refresh explainers + adding-a-feature with full binder signature
di-explainer and data-flow-explainer narratives still showed the
pre-ADR-014 1-arg bindProductionBlog(config) form. Updated both
explainers to (config, tracer, logger, bus, queue), and adjusted the
narrative arc so the production-swap step mentions the
resolveEventsAndJobs* preamble.

adding-a-feature's Step 16 sketch was the same 1-arg shape; replaced
with the canonical 5-arg signature and a short note that the bus/queue
params are accept-and-forward until gen event consume / gen job
generators inject usage at the anchors. Pointer to a real feature's
bind-production.ts for the complete reference.

Final sweep is clean — no stale (container, config) or 1-arg
bindProduction signatures remain in docs/.
2026-05-08 17:54:11 +02:00
5a2234f7ad docs: refresh per-package AGENTS.md + di-explainer to current binders
Six per-package AGENTS.md tables and the di-explainer wiring trace all
showed pre-ADR-014 binder shapes. Refreshed to the post-ADR-015
reality:

- Per-package "Public exports" tables now show the
  (config, tracer, logger, bus, queue) production signature and the
  (tracer, logger, bus, queue) dev-seed companion. auth and
  marketing-pages also list their newly-public ./di/container,
  ./di/symbols, and (marketing-pages only) ./services/mailer +
  ./services/recording-mailer subpaths.
- di-explainer's wiring trace adds the resolveEventsAndJobs* step in
  bindAll(), the (config, tracer, logger, bus, queue) binder
  signature, and the // <gen:event-handlers> / // <gen:jobs>
  injection sites.

No code change; pre-existing AGENTS.md staleness predates ADR-014 +
ADR-015 — this commit catches both up.
2026-05-08 17:51:35 +02:00
9e8ff6411c docs: align spec + dependency-flow with ADR-015
Three follow-up corrections from the docs audit:

- vertical-feature-spec § 13 dropped the events/jobs bullet from the
  "Out of scope (deferred)" list — they shipped, the bullet didn't
  belong there. The spec already mentions ADR-015 elsewhere (§ 4
  optional folders).

- vertical-feature-spec § 14 success criterion replaced "Any
  cross-feature import fails pnpm lint" with the post-ADR-015 reality:
  cross-feature imports are restricted to event contracts (the feature
  boundary tag now accepts feature-tagged deps), but rule E1
  (no-handler-reexport) keeps consumer handlers / use cases /
  repositories private.

- dependency-flow now shows the resolveEventsAndJobs* step in bindAll
  and the (config, tracer, logger, bus, queue) binder signature, plus
  the // <gen:event-handlers> / // <gen:jobs> anchor lines where the
  generators inject subscribe / register calls.
2026-05-08 17:45:24 +02:00
31fc667c97 Merge branch 'feat/events-and-jobs': cross-feature events and background jobs
Implements ADR-015 in 52 commits. Highlights:

- New @repo/core-events package: IEventBus, defineEvent + EventDescriptor,
  InMemoryEventBus, PayloadJobsEventBus.
- New @repo/core-shared/jobs subpath: IJobQueue, InMemoryJobQueue (with
  register), PayloadJobQueue.
- Recording test helpers (RecordingEventBus, RecordingJobQueue) in
  @repo/core-testing/instrumentation, using local-type-alias pattern to
  avoid build-graph cycles.
- ESLint rules E1 (no-handler-reexport) and J (no-direct-payload-jobs).
- Feature binders accept (bus, queue); apps/web-next bindAll resolves
  Payload-backed in production and in-memory in dev-seed.
- Three Plop generators: gen event publish, gen event consume, gen job.
  Each one threads through fixed // <gen:*> anchor comments enforced by
  a CI guard.
- Proof-of-life flow: signing up in @repo/auth publishes
  userSignedUpEvent on the shared bus; @repo/marketing-pages subscribes
  and enqueues a send-welcome-email job; the in-memory queue dispatches
  it to the wrapped job which records on RecordingMailerService. e2e
  test at apps/web-next/src/__tests__/sign-up-welcome-email.test.ts.
- Documentation: ADR-015, docs/guides/events-and-jobs.md, AGENTS.md +
  CLAUDE.md + scaffolding-guide updates, vertical-feature-spec deferred
  lines retired.

Workspace green at the merge boundary: typecheck (15/15), lint (16/16),
test (17/17), turbo boundaries (clean).
2026-05-08 17:26:21 +02:00
a98e41d080 chore: workspace green check (Task 56)
Three issues uncovered by the full pnpm typecheck/test/boundaries pass
and resolved here:

- core-testing was importing IEventBus / IJobQueue from core-events /
  core-shared, creating two boundary violations (tooling → core) and a
  build-graph cycle. Inlined the type aliases (mirroring how
  RecordingTracer / RecordingLogger handle ITracer / ILogger).
  recording-event-bus.test.ts replaces defineEvent() with an inline
  descriptor literal so no runtime import is needed either. core-events
  and core-shared are removed from core-testing dependencies.

- turbo.json: typecheck and test no longer dependsOn ^typecheck / ^build.
  Each package's tsc / vitest resolves cross-package types via
  node_modules independently, and dropping the topological dep avoids the
  spurious cycle warning that appeared once core-testing started
  importing core-events / core-shared.

- turbo.json: feature.dependencies.allow gains "feature". Cross-feature
  event flow (ADR-015) requires a consumer feature to import the
  publisher's event contract directly. The dangerous form (importing
  the publisher's handler/use-case/repo) is still blocked by E1's
  no-handler-reexport ESLint rule and the missing public exports.

- TaskConfig<"slug-string"> → TaskConfig<{ input; output }> in the gen
  job task template (and the shipped send-welcome-email.task.ts) since
  runtime-generated slugs aren't keys of TypedJobs['tasks'].
2026-05-08 17:21:47 +02:00
b3a2afec72 docs(spec): events/jobs no longer deferred (ADR-015) 2026-05-08 17:08:38 +02:00
1a9cd25746 docs(scaffolding): event/job generators reference 2026-05-08 17:08:08 +02:00
3a3b66f4ab docs(claude): generators + events/jobs conventions 2026-05-08 17:07:24 +02:00
ec11bfc77a docs(agents): events-and-jobs section + guide reference 2026-05-08 17:06:48 +02:00
f775725cb2 docs(guide): events-and-jobs walkthrough 2026-05-08 17:06:08 +02:00
c531b81544 docs(adr-015): cross-feature events and background jobs
Also folds in the spec correction noted in the plan's known follow-up
\#1: subscribe takes consumerFeature: string as a second arg between
descriptor and handler. § 3.3 and § 5.4 updated.
2026-05-08 17:05:05 +02:00
04899de98c test(web-next): e2e cross-feature sign-up→welcome-email flow
End-to-end proof-of-life. bindAllDevSeed wires InMemoryEventBus +
InMemoryJobQueue across all features. signUpController publishes
auth.user.signed-up; marketing-pages' handler enqueues
marketing-pages.send-welcome-email; the in-memory queue dispatches
the wrapped job which records on RecordingMailerService.

Adds ./di/container, ./di/symbols, ./services/mailer, and
./services/recording-mailer to auth + marketing-pages exports so
the e2e test can resolve from the per-feature containers without
deep-importing.
2026-05-08 16:37:37 +02:00
7e844c646d feat(marketing-pages): sendWelcomeEmail job + handler enqueues it
sendWelcomeEmailJob takes IMailerService, validates the typed input
(userId + email), and delegates to mailer.sendWelcome. The
onAuthUserSignedUpHandler now takes IJobQueue and enqueues
"marketing-pages.send-welcome-email" with the event payload.

Both binders bind a RecordingMailerService at the IMailerService
symbol (production placeholder until a real adapter ships) and pass
mailer / queue into the wrapped factories. Dev-seed additionally
queue.register()s the slug so the in-memory queue dispatches via
the wrapped job; production relies on the generated Payload event-task
to resolve the wrapped handler from the container.
2026-05-08 16:35:45 +02:00
6b57a34c0c feat(marketing-pages): subscribe to auth.user.signed-up
Generated handler + Payload event-task via gen event consume,
threaded through symbols / both binders / cms re-export at the
configured anchors. bus.subscribe wires the in-memory delivery in
dev-seed; the __events.auth.user.signed-up.marketing-pages Payload
task closes the production-bus loop.

Also fixes two generator-level issues found during Phase 8:
- Drop publisher prompt's `when` clause so --args can supply the
  4th positional argument (Plop limitation: --args cannot bypass
  conditional prompts). Validate runs only in consume mode.
- Switch event-task.ts.hbs from TaskConfig<"slug-string"> to
  TaskConfig<{ input: ...; output: object }> since runtime-generated
  event slugs are not keys of TypedJobs['tasks'].
2026-05-08 16:33:14 +02:00
c1b8a7e434 feat(auth): signUp publishes userSignedUpEvent
signUpUseCase now takes an IEventBus and publishes userSignedUpEvent
after creating the user (synthesizing email from username since auth
is username-based). Use case mocks-default in module.ts get a fresh
InMemoryEventBus per resolution; bind-production / bind-dev-seed wire
the shared bus passed by bindAll. Tests updated to inject
RecordingEventBus, including a new test that asserts publish on
success and silence on failure.
2026-05-08 16:28:21 +02:00
7f63adf740 feat(auth): userSignedUpEvent contract 2026-05-08 16:24:12 +02:00