Skip to content

feat(mktg-os): brand kit one-page intake form and generation flow - #1348

Open
joanreyero wants to merge 4 commits into
mainfrom
feat/LFXV2-2059-brand-kit-ui
Open

feat(mktg-os): brand kit one-page intake form and generation flow#1348
joanreyero wants to merge 4 commits into
mainfrom
feat/LFXV2-2059-brand-kit-ui

Conversation

@joanreyero

Copy link
Copy Markdown
Contributor

Summary

Adds the Brand Kit agent-invocation flow to the dark-launched Marketing OS surface (LFXV2-2059, WorkItem wi-brand-kit-lfx-selfserve) — display-only, no persistence (the dec-brand-kit-storage-v1 write path is deferred; the session-driver and envelope-validation code is salvaged from the closed #1317).

What it does

  • One form, one page (dec-brand-kit-intake-form): all 7 of Paul's intake questions rendered verbatim as a single open-ended form on the flag-gated /…/mktg-os-agents page. New brand-kit catalog tile opens the form surface instead of the chat stub (surface: 'brand-kit-form' discriminator).
  • POST /api/mktg-agents/brand-kit/generate: validates the 7 answers server-side (all keys, non-empty, no extras), renders the agent's documented batch-intake message, and starts a one-shot Guild form-mode session against linux-foundation~brand-kit (handle from the server-side catalog, never the client). Returns {sessionId, ownerToken} (HMAC creator binding).
  • POST /api/mktg-agents/brand-kit/result: owner-token-gated poll. Scans raw session events for the finalize_brand_kit tool-result envelope (live-smoke A3 verdict — the __submit__ text is never trusted), validates it (contract schema, 12-heading order gate, server-side sha256 recompute; mismatch → still pending), and returns the document.
  • Client: form → generating state (aria-live) → sanitized markdown render (lfx-markdown-renderer) + .md download + start-over. Poll: 10 s cadence, ~5 min cap, tolerates 3 consecutive transient failures, generation-epoch token discards stale in-flight responses after cancel/reset.

Review process

Post-commit reviewer trio + full-branch sweep run per repo work cycle: 0 Critical; both Important findings (stale-poll race, transient-failure abort) fixed in the fix(review) commit; sweep verified the epoch fix closes the race.

Documented trade-offs

  • Diff size (+1399) above the 1000-line target: ~217 lines are tests and ~220 the shared contract transcription (interface + constants); the surface is one atomic feature/contract, splitting would sever the contract from its consumer.
  • 500-raw-event page cap is warn-only (loud server warning; pagination follow-up if real sessions approach it — current sessions emit <10 events).
  • No server-service unit spec (repo convention — no existing server/services/*.spec.ts); non-trivial logic lives in @lfx-one/shared utils with a 20-test vitest spec.
  • No e2e for the new surface yet — dark-launched; e2e lands with the un-dark-launch story.
  • Component-local poll-tuning consts kept module-level per existing repo precedent (badges/trainings dashboards).

External references

  • Contract: marketing-os-agents/docs/contracts/brand-kit-output.{md,schema.json} (normative; shared interface is its transcription)
  • Producer agent: linux-foundation~brand-kit v1.0.14 (Guild, PUBLISHED — verified live)
  • Salvage source: closed PR feat(mktg-os): brand kit session-consumer persistence endpoint #1317 (persistence deferred, not rejected)

…LFXV2-2059)

Agent-invocation UI without persistence (persistence deferred per Joan's ruling; salvaged session-driver + envelope-extraction from the closed PR #1317 branch, storage parts dropped):

- One-page intake form (all 7 of Paul's questions verbatim, open-ended, per dec-brand-kit-intake-form) on the dark-launched mktg-os-agents surface (feature flag unchanged).
- POST /api/mktg-agents/brand-kit/generate renders the batch-intake message and starts a one-shot Guild form-mode session vs linux-foundation~brand-kit; POST .../result polls, extracts the finalize_brand_kit tool-result envelope from raw events, validates (schema + 12-heading gate + server-side sha256 recompute), and returns the document for display.
- Client renders the validated markdown with a download option; owner-token creator binding on the result endpoint. NO PutObject, NO object storage.
- Shared brand-kit contract types/constants/utils reused from the salvage branch plus intake-form validation + batch-message renderer; 20-test vitest spec.

Signed-off-by: Joan Reyero <joan@reyero.io>
- Generation-epoch token discards stale in-flight poll responses so a cancelled/reset generation can't resurrect (reviewer Important).
- Tolerate up to 3 consecutive transient poll failures instead of failing the multi-minute generation on one blip (reviewer Important).
- Split the onSubmit guards so generating() no longer marks the form touched.
- role=status + aria-live=polite on the generating state.
- path context on the brand_kit_result AuthorizationError; reword stale persistence-era comments (persistence deferred).

Signed-off-by: Joan Reyero <joan@reyero.io>
@joanreyero
joanreyero requested a review from a team as a code owner August 7, 2026 09:06
Copilot AI balanced review requested due to automatic review settings August 7, 2026 09:06
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches authenticated BFF endpoints, session owner tokens, and Guild event parsing with integrity checks; display-only but incorrect validation could surface bad agent output or deny legitimate results.

Overview
Brand Kit self-serve flow on the Marketing OS marketplace: selecting the new Brand Kit tile opens a one-page intake form (Paul’s seven questions) instead of the chat stub, via a surface: 'brand-kit-form' catalog discriminator.

The UI submits answers, shows a generating state, then polls the BFF for a validated document, renders markdown, supports .md download and start-over, with poll epoching to drop stale responses after cancel/navigation.

BFF: POST …/brand-kit/generate validates intake, starts a Guild form-mode session with a server-rendered batch message and catalog handle; POST …/brand-kit/result is owner-token gated, scans raw Guild events (excluding user/trigger events), extracts finalize_brand_kit envelopes, validates contract + headings, and recomputes SHA-256 before returning ready or pending.

Shared brand-kit-output/v1 constants, interfaces, and validation/extraction utils back both layers; lfx-textarea gains optional aria-describedby / aria-invalid for form errors.

Reviewed by Cursor Bugbot for commit 00c9151. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 34c7d37e-030d-48ca-8858-387573d85996

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Copilot AI 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.

Pull request overview

Adds a display-only Brand Kit generation flow to the dark-launched Marketing OS marketplace.

Changes:

  • Adds the seven-question intake and generated Markdown result UI.
  • Adds authenticated Guild generation and owner-gated polling endpoints.
  • Adds shared Brand Kit contracts, validation, extraction, and tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/shared/src/utils/index.ts Exports Brand Kit utilities.
packages/shared/src/utils/brand-kit.utils.ts Validates, extracts, and renders Brand Kit data.
packages/shared/src/utils/brand-kit.utils.spec.ts Tests shared Brand Kit utilities.
packages/shared/src/interfaces/mktg-agent.interface.ts Adds agent surface selection.
packages/shared/src/interfaces/index.ts Exports Brand Kit interfaces.
packages/shared/src/interfaces/brand-kit.interface.ts Defines Brand Kit API and envelope contracts.
packages/shared/src/constants/mktg-os-agents.constants.ts Registers the Brand Kit agent.
packages/shared/src/constants/index.ts Exports Brand Kit constants.
packages/shared/src/constants/brand-kit.constants.ts Defines intake and validation constants.
apps/lfx-one/src/server/services/guild.service.ts Fetches raw Guild session events.
apps/lfx-one/src/server/services/brand-kit.service.ts Drives generation and resolves results.
apps/lfx-one/src/server/routes/mktg-agents.route.ts Registers Brand Kit endpoints.
apps/lfx-one/src/server/controllers/mktg-agents.controller.ts Validates and handles Brand Kit requests.
apps/lfx-one/src/app/shared/services/brand-kit.service.ts Adds the Angular API client.
apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.ts Selects the Brand Kit form surface.
apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.html Renders the selected surface.
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.ts Implements form, polling, and download behavior.
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html Renders intake, progress, errors, and results.
Suppressed comments (1)

apps/lfx-one/src/server/controllers/mktg-agents.controller.ts:220

  • A request with no JSON body or a literal null body throws here before the sessionId type gate runs, producing a server error rather than the intended validation response. Normalize the body before destructuring.
    const { sessionId, ownerToken } = req.body as BrandKitResultRequest;

Comment thread packages/shared/src/utils/brand-kit.utils.ts Outdated
Comment thread apps/lfx-one/src/server/controllers/mktg-agents.controller.ts Outdated
Comment thread apps/lfx-one/src/server/services/brand-kit.service.ts
Comment thread packages/shared/src/utils/brand-kit.utils.ts Outdated
Comment thread apps/lfx-one/src/server/services/brand-kit.service.ts
@dealako

dealako commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@joanreyero you have AI feedback and conflicts to resolve.

- SECURITY: exclude user-originated events (user_message/trigger_message) from the raw-event scan so a schema-valid envelope planted in an intake answer can never be selected as agent output.
- Free-text scanner unwraps the envelope_json tool-result wrapper (walks outward brace by brace), closing the documented A3 path in non-JSON stream bodies.
- Extractor returns unknown[] candidates; BrandKitEnvelope typing now flows only through validateBrandKitEnvelope (no lying casts).
- Null-body normalization on both new endpoints (controlled 400 instead of a destructuring throw).
- Poll error tolerance off-by-one: three consecutive failures are now tolerated, the fourth aborts.
- trimmedRequired() on all seven intake controls (client/server validation parity for whitespace-only answers).
- a11y: aria-describedby + aria-invalid wired through lfx-textarea to the per-question error message.
- New brand-kit.service.spec.ts (6 tests): batch-message session start, pending, ready via envelope_json, sha-mismatch suppression, version preference, abridged-output rejection. Shared spec extended (21 tests).

Signed-off-by: Joan Reyero <joan@reyero.io>
Copilot AI review requested due to automatic review settings August 9, 2026 01:06

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 62bd856. Configure here.

Comment thread apps/lfx-one/src/server/services/guild.service.ts Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/shared/src/utils/brand-kit.utils.ts:47

  • project_name is never runtime-validated even though this validator later permits the candidate to be cast to BrandKitEnvelope. A schema-valid-looking envelope with project_name: { ... } therefore reaches BrandKitService.getResult() and is returned as projectName, violating the response contract and rendering [object Object] in the result header. Validate the optional field's type before accepting the envelope.
  if (typeof envelope.project !== 'string' || !BRAND_KIT_PROJECT_SLUG_REGEX.test(envelope.project)) {
    errors.push('project must be a lowercase kebab-case slug');
  }

apps/lfx-one/src/server/services/guild.service.ts:175

  • The truncation check counts events after filtering out user_message and trigger_message. A full 500-event API page containing either excluded type is therefore reported as fewer than 500 events, suppressing the only warning that the terminal envelope may be missing. Check and log the raw page length instead.
    if (items.length >= GUILD_RAW_EVENTS_LIMIT) {
      logger.warning(req, 'guild_get_raw_events', 'Raw event page is full — session may be truncated', { count: items.length, limit: GUILD_RAW_EVENTS_LIMIT });

apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html:32

  • The document is retained in the Guild session event stream—the result endpoint polls that stored stream—so telling users it is “not stored anywhere else” is inaccurate. Clarify that this iteration does not persist it to LFX document/object storage rather than implying no external retention.
            <span class="text-xs text-gray-500">Review the document below. Download it to keep a copy — it is not stored anywhere else yet.</span>

apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.ts:125

  • This calls browser-only URL and document APIs without a browser guard. The repository's SSR rule requires these references to be gated (.claude/rules/ssr-safety.md:14-28); add an isPlatformBrowser check before constructing the download.
    const blob = new Blob([current.documentMarkdown], { type: 'text/markdown;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');

Signed-off-by: Joan Reyero <joan@reyero.io>
Copilot AI review requested due to automatic review settings August 9, 2026 06:04

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/shared/src/utils/brand-kit.utils.ts:47

  • project_name is returned to Angular as a string, but this validator never checks its runtime type. A malformed envelope can therefore pass validation with an object or array here and violate BrandKitResultResponse.projectName; reject a present non-string value before casting the candidate to BrandKitEnvelope.
  if (typeof envelope.project !== 'string' || !BRAND_KIT_PROJECT_SLUG_REGEX.test(envelope.project)) {
    errors.push('project must be a lowercase kebab-case slug');
  }

apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html:32

  • The document necessarily remains in Guild session events—the BFF polls those events to retrieve it—so “not stored anywhere else” gives users an inaccurate data-retention assurance. Clarify that this iteration does not save it to LFX Brand Kit storage rather than implying no external copy exists.
            <span class="text-xs text-gray-500">Review the document below. Download it to keep a copy — it is not stored anywhere else yet.</span>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants