Initial TrueGrowth source import
This commit is contained in:
70
openspec/changes/add-social-auto-publishing/design.md
Normal file
70
openspec/changes/add-social-auto-publishing/design.md
Normal file
@@ -0,0 +1,70 @@
|
||||
## Context
|
||||
The referenced project, `dreammis/social-auto-upload`, currently documents `sau` CLI commands as the main path and keeps the historical web UI as legacy. Its active model supports `login`, `check`, `upload-video`, and `upload-note` commands for core platforms such as Douyin, Kuaishou, Xiaohongshu, Bilibili, and YouTube, with additional uploader modules for Video Channel, Baijiahao, TikTok, and others.
|
||||
|
||||
TrueGrowth already has a React/Nx workbench shell, a left navigation, local runtime bridges, task queues, prompt history result previews, and unified asset concepts. The integration should feel native to TrueGrowth instead of exposing a foreign web app.
|
||||
|
||||
The historical `sau_frontend` is still useful as a workflow reference. Its actual pages are Dashboard, AccountManagement, MaterialManagement, PublishCenter, and About. The behaviors to carry forward are account status loading/validation, QR/SSE login feedback, material upload/preview/selection, tabbed publish drafts, per-draft account/platform/topic/schedule configuration, batch publishing progress, cancellation, and per-draft success/failure reporting.
|
||||
|
||||
## Goals
|
||||
- Let users review all publishable finished images and videos in one place.
|
||||
- Let users authorize and manage multiple social accounts per platform.
|
||||
- Let users compose one publishing draft and fan it out to multiple channels/accounts.
|
||||
- Let users publish immediately or schedule supported platforms.
|
||||
- Reuse `social-auto-upload` behavior through a local bridge so platform automation remains isolated and upgradeable.
|
||||
- Preserve a clear audit trail from generated asset to publish job to channel result.
|
||||
|
||||
## Non-Goals
|
||||
- Do not copy the legacy `sau_frontend` UI into TrueGrowth.
|
||||
- Do not promise every upstream uploader has the same maturity; surface unsupported fields and unsupported media types per channel.
|
||||
- Do not store raw cookies or credentials in frontend state.
|
||||
- Do not block the proposal on a remote cloud service; the first implementation can use the local runtime.
|
||||
|
||||
## Decisions
|
||||
- Decision: Create a native TrueGrowth publish workspace with tabs for "发布任务" and "账号授权"; publishable asset selection lives inside the publish-task form and publish history lives beside the form.
|
||||
- Decision: Represent publishable content as normalized `PublishableAsset` records derived from existing tasks/assets/history rather than duplicating large media blobs.
|
||||
- Decision: Add a local runtime adapter with operations equivalent to `sau <platform> login`, `check`, `upload-video`, and `upload-note`.
|
||||
- Decision: Start with the CLI-supported stable channels from upstream CLI docs: Douyin, Kuaishou, Xiaohongshu, and Bilibili. YouTube can be visible only after the local bridge exposes its CLI command surface. Other upstream uploaders can appear as planned/experimental until their command wrappers are implemented.
|
||||
- Decision: Store only account aliases, platform, status, cookie/session file references, last check time, and capability metadata in app-facing state. Secrets remain under the local runtime's controlled directory.
|
||||
- Decision: Replace the upstream "multiple publish tabs" UI with TrueGrowth "campaign drafts": each draft can contain selected finished assets and multiple channel/account targets, while still preserving the upstream ability to add several draft groups and batch-submit them with progress.
|
||||
|
||||
## UI Shape
|
||||
- Left nav item: `自动发布`, near "素材" and "任务" because it operates on finished content.
|
||||
- Dashboard header: connected account counts, abnormal/expired account counts, enabled channel counts, publishable asset counts, queued jobs, failed jobs, and recent publish results.
|
||||
- Asset center grid/list: first-level filters for 素材, 作品, 已发布 plus media type and source tags.
|
||||
- Composer: selected assets, local upload fallback, asset-center picker, title, description/note, intelligent publish copy helper, tags/topics, original declaration, channel-specific options, account selector, schedule selector, validation summary, and submit action.
|
||||
- Account page: platform filters, search, account rows, login/check actions, QR or browser-login progress, last status, and capability badges for video, image-note, schedule, cover, draft-only, and product-link support.
|
||||
- Job page: queue grouped by publishing campaign, showing per-channel progress, logs, retries, cancellation, and source asset links.
|
||||
|
||||
## Upstream Workflow Mapping
|
||||
- Dashboard maps to TrueGrowth summary cards and quick actions inside the auto-publishing workspace.
|
||||
- AccountManagement maps to "账号授权": quick load accounts first, run background status validation, allow add/edit/delete, re-login failed accounts, and display login progress or QR guidance.
|
||||
- MaterialManagement maps to the route-level "资产中心" plus the publish-task asset picker: TrueGrowth generated assets are first-class, while manual upload remains available for assets not created in the system.
|
||||
- PublishCenter maps to "发布任务": upstream publish tabs become campaign drafts; local upload and asset-center selection become the publish content picker; account/platform/topic/schedule/original/product/draft controls become target-aware composer sections.
|
||||
- Upstream `/postVideo` single payload maps to TrueGrowth `PublishCampaign` plus one `PublishJob` per account/platform target so mixed success and retry are tracked precisely.
|
||||
|
||||
## Runtime Bridge
|
||||
- The frontend calls a local TrueGrowth runtime endpoint, not the upstream CLI directly.
|
||||
- The runtime bridge validates file availability, exports remote/blob assets to a local file when required by `sau`, invokes the platform command, streams logs, and records result status.
|
||||
- Platform capabilities are data-driven so new upstream support can be enabled without rewriting the page.
|
||||
- Login operations may open an interactive browser or show QR guidance depending on the platform; the UI must reflect that the user may need to complete authorization outside the page.
|
||||
- Login/status operations should support the upstream legacy behavior pattern of fast account listing plus slower validation. If the bridge can emit QR or browser-login progress, the UI should stream it; otherwise it should show the command/log state and required user action.
|
||||
- Upload command options must map channel-specific CLI fields, including `--schedule`, `--thumbnail`, `--thumbnail-landscape`, `--thumbnail-portrait`, Douyin `--product-link` and `--product-title`, and Bilibili `--tid`.
|
||||
|
||||
## Risks / Trade-offs
|
||||
- Browser automation can be fragile as platforms change. Mitigation: show per-channel health, command logs, retries, and account check status.
|
||||
- Some generated media may only exist as remote URLs or cache URLs. Mitigation: add an export/materialization step before enqueueing upload jobs.
|
||||
- Schedules differ across platforms. Mitigation: validate per target and allow fallback to immediate publish or mark unsupported before submission.
|
||||
- Multi-account parallelism can trigger platform risk controls. Mitigation: provide concurrency limits per platform/account.
|
||||
|
||||
## Migration Plan
|
||||
1. Introduce the UI route and local data models behind a workbench page.
|
||||
2. Implement read-only finished asset aggregation.
|
||||
3. Implement account authorization bridge and status checks.
|
||||
4. Implement publish draft creation and local queue persistence.
|
||||
5. Connect stable `sau` commands for video and image-note publishing.
|
||||
6. Add job logs, retries, and publish history.
|
||||
|
||||
## Open Questions
|
||||
- Should the first release install or vendor `social-auto-upload`, or should it detect a user-provided `sau` binary/path?
|
||||
- Which platforms are required for the first production milestone beyond Douyin, Kuaishou, Xiaohongshu, Bilibili, and YouTube?
|
||||
- Should publish history sync into the existing task center, or remain scoped to the auto-publishing workspace?
|
||||
25
openspec/changes/add-social-auto-publishing/proposal.md
Normal file
25
openspec/changes/add-social-auto-publishing/proposal.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Change: Add social auto publishing workspace
|
||||
|
||||
## Why
|
||||
TrueGrowth can generate and collect media across local uploads, AI image/video, digital human, clipping, canvas, and publishing workflows, but the previous split between "素材库" and "成品库" made assets hard to reuse. Operators need a unified "资产中心" plus a publish workspace that can authorize accounts and submit real social publishing jobs through the bundled `social-auto-upload` runtime.
|
||||
|
||||
## What Changes
|
||||
- Upgrade the left-navigation "素材库" into "资产中心" with first-level sections for "素材", "作品", and "已发布".
|
||||
- Use published state as an asset marker and filter, not a separate copied library.
|
||||
- Keep "发布中心" focused on two tabs: "发布任务" and "账号授权".
|
||||
- Move publishable asset selection into the publish-task form and remove the separate publish-center "成品库" tab.
|
||||
- Add social account authorization management for multiple accounts per channel, including login, status check, expiration/error state, and account grouping.
|
||||
- Add publish draft and schedule flows for video and image-note content, with channel-specific fields mapped from `dreammis/social-auto-upload` command capabilities.
|
||||
- Integrate the bundled `vendor/social-auto-upload` source through a local runtime bridge; users must not be asked to install `sau` separately.
|
||||
- Add publish job tracking, retry, cancellation, logs, and per-channel result history that connect back to the source TrueGrowth asset.
|
||||
|
||||
## Impact
|
||||
- Affected specs: social-auto-publishing, asset-center
|
||||
- Affected code:
|
||||
- `apps/web/src/workbench/WorkbenchShell.tsx`
|
||||
- `apps/web/src/workbench/app-definitions.tsx`
|
||||
- `apps/web/src/workbench/truegrowth.types.ts`
|
||||
- `apps/web/src/workbench/social-publishing.logic.ts`
|
||||
- Local runtime API in `scripts/truegrowth-local-api.mjs` or a sibling runtime bridge for `sau`
|
||||
- Bundled runtime source under `vendor/social-auto-upload`
|
||||
- Task/history/media aggregation around `packages/drawnix/src/services/task-storage-reader.ts`, `packages/drawnix/src/services/prompt-history-service.ts`, and existing asset/media-library services
|
||||
@@ -0,0 +1,173 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Unified Asset Center
|
||||
The system SHALL expose a first-class "资产中心" that replaces the old navigation-facing "素材库" while preserving the real media-library data source.
|
||||
|
||||
#### Scenario: User opens the asset center
|
||||
- **WHEN** the user opens `/assets`
|
||||
- **THEN** the page title and navigation label show "资产中心"
|
||||
- **AND** the page provides first-level sections for "素材", "作品", and "已发布"
|
||||
- **AND** the page keeps media filters for image, video, and audio assets
|
||||
|
||||
#### Scenario: Published assets are filtered by state
|
||||
- **GIVEN** an asset was included in a successful publish target
|
||||
- **WHEN** the user opens the "已发布" asset section
|
||||
- **THEN** the asset is visible through its published marker
|
||||
- **AND** the system does not create a duplicate copied asset library
|
||||
|
||||
#### Scenario: Asset source tags are available
|
||||
- **WHEN** assets are displayed or selected for publishing
|
||||
- **THEN** the system can distinguish source tags for "主体", "本地", "AI", "数字人", and "剪辑"
|
||||
- **AND** legacy assets without new metadata remain compatible by falling back to local or AI source information
|
||||
|
||||
### Requirement: Auto Publishing Navigation
|
||||
The system SHALL provide a first-class workbench entry named "自动发布" in the left navigation and application center.
|
||||
|
||||
#### Scenario: User opens auto publishing
|
||||
- **WHEN** the user clicks "自动发布" in the left navigation
|
||||
- **THEN** the workbench shows the auto publishing workspace
|
||||
- **AND** the page uses the same visual system, navigation behavior, theme behavior, and responsive layout conventions as the existing workbench pages
|
||||
|
||||
### Requirement: Unified Finished Asset Hub
|
||||
The system SHALL aggregate publishable finished image and video outputs from digital avatar, AI canvas, AI image, AI video, clipping, media library, and task/history sources into the asset center and publish-task selector.
|
||||
|
||||
#### Scenario: Completed generated media appears in the hub
|
||||
- **GIVEN** an image or video generation task has completed with a result URL, thumbnail, or local asset reference
|
||||
- **WHEN** the user opens the asset center "作品" view or the publish-task asset selector
|
||||
- **THEN** the media appears as a publishable asset
|
||||
- **AND** the item shows its media type, source module, title, created time, preview, and existing publish state
|
||||
|
||||
#### Scenario: Duplicate media is normalized
|
||||
- **GIVEN** the same media is visible through task history and the media library
|
||||
- **WHEN** the finished asset hub is loaded
|
||||
- **THEN** the media appears once
|
||||
- **AND** the item keeps references to every known source record
|
||||
|
||||
### Requirement: Multiple Social Account Authorization
|
||||
The system SHALL let users manage multiple authorized accounts per social channel.
|
||||
|
||||
#### Scenario: User authorizes a channel account
|
||||
- **WHEN** the user starts login for a supported channel account alias
|
||||
- **THEN** the system starts the local social upload authorization flow
|
||||
- **AND** the account row records platform, alias, status, last check time, and supported publishing capabilities
|
||||
- **AND** the frontend does not store raw cookies or secrets
|
||||
|
||||
#### Scenario: User checks account status
|
||||
- **WHEN** the user checks an existing account
|
||||
- **THEN** the system verifies authorization through the local social upload bridge
|
||||
- **AND** the UI shows whether the account is available, expired, failed, or needs user action
|
||||
|
||||
#### Scenario: Account list loads before validation completes
|
||||
- **WHEN** the user opens account authorization
|
||||
- **THEN** the system may show cached or quick-loaded account rows immediately
|
||||
- **AND** slower authorization validation updates each row without blocking the page
|
||||
|
||||
#### Scenario: Login requires QR or browser action
|
||||
- **WHEN** the local bridge reports QR data, browser-login progress, or a user-action message
|
||||
- **THEN** the account dialog shows that progress in place
|
||||
- **AND** updates the account row when the bridge reports success or failure
|
||||
|
||||
### Requirement: Multi-Channel Publish Composer
|
||||
The system SHALL allow users to select one or more assets from the asset center or local upload and create a publish draft targeting multiple channels and accounts.
|
||||
|
||||
#### Scenario: User prepares a video campaign
|
||||
- **GIVEN** the user selected a video asset
|
||||
- **WHEN** the user selects multiple authorized channel accounts and enters title, description, tags, and optional schedule time
|
||||
- **THEN** the composer validates channel requirements
|
||||
- **AND** the user can enqueue one campaign containing one publish target per selected account
|
||||
|
||||
#### Scenario: User prepares an image-note campaign
|
||||
- **GIVEN** the user selected one or more image assets
|
||||
- **WHEN** the user selects channels that support image-note publishing and enters title, note body, tags, and optional schedule time
|
||||
- **THEN** the composer validates channel requirements
|
||||
- **AND** unsupported channels are disabled or reported before submission
|
||||
|
||||
#### Scenario: User manages multiple campaign drafts
|
||||
- **WHEN** the user creates several publishing drafts before submission
|
||||
- **THEN** the composer keeps each draft's selected assets, accounts, platform options, title, text, topics, and schedule independently
|
||||
- **AND** the user can submit drafts individually or as a batch
|
||||
|
||||
#### Scenario: User chooses generated or uploaded material
|
||||
- **WHEN** the user adds media to a draft
|
||||
- **THEN** the system offers asset-center content first
|
||||
- **AND** allows local upload fallback for media that was not generated in TrueGrowth
|
||||
|
||||
#### Scenario: User opens publish center
|
||||
- **WHEN** the user opens `/publish`
|
||||
- **THEN** only "发布任务" and "账号授权" tabs are visible
|
||||
- **AND** "发布任务" is the default view
|
||||
- **AND** publish records are shown beside the publish form, not as a separate tab
|
||||
|
||||
#### Scenario: User drafts social copy
|
||||
- **WHEN** the user edits a video description or image-note body
|
||||
- **THEN** the system provides knowledge-base fill and an intelligent copywriting dialog
|
||||
- **AND** generated copy is shaped for external social publishing rather than internal generation prompts
|
||||
|
||||
### Requirement: Social Auto Upload Runtime Bridge
|
||||
The system SHALL integrate bundled `vendor/social-auto-upload` source through a local runtime bridge compatible with the upstream `sau` command flow.
|
||||
|
||||
#### Scenario: Runtime resolves bundled sau
|
||||
- **WHEN** the local runtime starts or checks publishing status
|
||||
- **THEN** it resolves `sau` from `SOCIAL_AUTO_UPLOAD_COMMAND` only when explicitly configured for development
|
||||
- **AND** otherwise runs the bundled `vendor/social-auto-upload/sau_cli.py`
|
||||
- **AND** it does not depend on a system PATH `sau`
|
||||
|
||||
#### Scenario: Bundled sau source is missing
|
||||
- **WHEN** `vendor/social-auto-upload` or its CLI entry is missing
|
||||
- **THEN** the runtime reports an internal bundled publishing module error
|
||||
- **AND** the UI must not instruct the user to install `sau` manually
|
||||
|
||||
#### Scenario: Runtime invokes video upload
|
||||
- **GIVEN** a publish target uses a channel account with video upload support
|
||||
- **WHEN** the job starts
|
||||
- **THEN** the runtime materializes the media to a local file if needed
|
||||
- **AND** invokes the equivalent `sau <platform> upload-video` command with account, file, title, description, tags, and supported channel options
|
||||
- **AND** streams status and logs back to the workbench
|
||||
|
||||
#### Scenario: Runtime invokes image-note upload
|
||||
- **GIVEN** a publish target uses a channel account with image-note support
|
||||
- **WHEN** the job starts
|
||||
- **THEN** the runtime invokes the equivalent `sau <platform> upload-note` command with account, image files, title, note body, tags, and supported channel options
|
||||
- **AND** streams status and logs back to the workbench
|
||||
|
||||
#### Scenario: Runtime maps channel-specific options
|
||||
- **GIVEN** a target includes supported options such as schedule time, cover image, Bilibili partition, Douyin product link, Douyin product title, original declaration, or draft-only intent
|
||||
- **WHEN** the runtime builds the upload command
|
||||
- **THEN** it passes only the options supported by that target channel
|
||||
- **AND** records omitted unsupported options in validation output before the job starts
|
||||
|
||||
### Requirement: Publish Job Tracking
|
||||
The system SHALL track publishing campaigns and per-channel jobs from draft submission through completion.
|
||||
|
||||
#### Scenario: One target fails in a multi-channel campaign
|
||||
- **GIVEN** a campaign has multiple publish targets
|
||||
- **WHEN** one target fails and another succeeds
|
||||
- **THEN** the campaign remains visible with mixed status
|
||||
- **AND** each target shows its own status, error/log summary, retry action, and source asset reference
|
||||
|
||||
#### Scenario: User retries a failed target
|
||||
- **GIVEN** a publish target failed
|
||||
- **WHEN** the user retries it
|
||||
- **THEN** the system reuses the original asset and target metadata
|
||||
- **AND** appends a new attempt to the target history
|
||||
|
||||
#### Scenario: User cancels batch publishing
|
||||
- **GIVEN** multiple campaign drafts are being submitted as a batch
|
||||
- **WHEN** the user cancels the batch
|
||||
- **THEN** the system stops enqueueing remaining drafts where possible
|
||||
- **AND** shows completed, failed, cancelled, and not-started target counts separately
|
||||
|
||||
### Requirement: Channel Capability Guardrails
|
||||
The system SHALL expose platform-specific publishing capabilities and prevent invalid submissions.
|
||||
|
||||
#### Scenario: Unsupported schedule is selected
|
||||
- **GIVEN** a selected channel does not support scheduled publishing through the configured bridge
|
||||
- **WHEN** the user selects a scheduled publish time
|
||||
- **THEN** the composer warns that the target cannot use scheduling
|
||||
- **AND** the target must be changed, excluded, or switched to immediate publish before enqueueing
|
||||
|
||||
#### Scenario: Unsupported media type is selected
|
||||
- **GIVEN** a selected channel account does not support the selected media type
|
||||
- **WHEN** the user validates the draft
|
||||
- **THEN** the target is marked invalid
|
||||
- **AND** no job is created for that target until the issue is resolved
|
||||
51
openspec/changes/add-social-auto-publishing/tasks.md
Normal file
51
openspec/changes/add-social-auto-publishing/tasks.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## 1. Proposal Gate
|
||||
- [x] 1.1 Review and approve this OpenSpec change before implementation.
|
||||
- [x] 1.2 Decide whether `social-auto-upload` is vendored, installed by setup, or referenced through a configured local path.
|
||||
- [x] 1.3 Confirm first-release channel list and whether experimental channels should be visible.
|
||||
|
||||
## 2. Data Model And Aggregation
|
||||
- [x] 2.1 Define `PublishableAsset`, `SocialAccount`, `PublishDraft`, `PublishCampaign`, `PublishTarget`, and `PublishJob` types.
|
||||
- [x] 2.2 Add a service that aggregates completed image/video outputs from task queue summaries, prompt history previews, media library assets, digital avatar outputs, and canvas media exports.
|
||||
- [x] 2.3 Add de-duplication by canonical media URL/file reference and preserve source links back to task/asset/project/canvas.
|
||||
- [x] 2.4 Add filters for source module, media type, publish status, date, project, and keyword.
|
||||
- [x] 2.5 Add platform capability metadata for video, image-note, schedule, cover images, Bilibili partition `tid`, Douyin product fields, and draft-only behavior.
|
||||
- [x] 2.6 Add publish history state linking each asset to campaigns, targets, attempts, and result logs.
|
||||
|
||||
## 3. Local Runtime Bridge
|
||||
- [x] 3.1 Add local runtime endpoints for platform capability discovery, account login, account check, publish enqueue, job status, logs, retry, and cancel.
|
||||
- [x] 3.2 Wrap `sau` command forms for `login`, `check`, `upload-video`, and `upload-note`.
|
||||
- [x] 3.3 Materialize remote/blob/cache media into local files before invoking upload commands.
|
||||
- [x] 3.4 Keep cookies/session files outside frontend storage and expose only safe metadata to the UI.
|
||||
- [x] 3.5 Add per-platform/account concurrency limits and command timeout handling.
|
||||
- [x] 3.6 Support fast account listing plus background validation, matching the useful legacy frontend pattern.
|
||||
- [x] 3.7 Stream login progress, QR data or required-user-action messages, command logs, and final status to the frontend.
|
||||
- [x] 3.8 Map CLI options for `--schedule`, cover thumbnails, Douyin product link/title, Bilibili `--tid`, and headless/headed/debug execution preferences.
|
||||
|
||||
## 4. Workbench UI
|
||||
- [x] 4.1 Add `social.publish` capability and a left nav item labeled "自动发布".
|
||||
- [x] 4.2 Add an app definition card for "自动发布".
|
||||
- [x] 4.3 Build the auto-publishing page using the project workbench style and existing lucide/TDesign patterns.
|
||||
- [x] 4.4 Build tabs for "发布任务" and "账号授权", with asset selection and publish records embedded in the publish-task page.
|
||||
- [x] 4.5 Build a publish composer that supports selected assets, captions, tags, account targets, per-channel overrides, schedule time, and validation.
|
||||
- [x] 4.6 Build status cards and tables for accounts, queued jobs, logs, and publish results.
|
||||
- [x] 4.7 Build campaign draft tabs/groups inspired by upstream PublishCenter while using TrueGrowth native layout.
|
||||
- [x] 4.8 Build finished-asset picker and manual local upload fallback inspired by upstream material selection.
|
||||
- [x] 4.9 Build account platform filters, search, add/edit/delete, re-login, QR/progress display, and background validation indicators.
|
||||
- [x] 4.10 Build topic/tag controls with custom and recommended entries, plus channel-specific composer sections.
|
||||
|
||||
## 5. Publishing Behavior
|
||||
- [x] 5.1 Support video publishing to stable CLI-backed channels.
|
||||
- [x] 5.2 Support image-note publishing where the channel supports `upload-note`.
|
||||
- [x] 5.3 Validate channel-specific required fields before enqueueing.
|
||||
- [x] 5.4 Track per-target success/failure without failing the entire campaign when only one channel fails.
|
||||
- [x] 5.5 Connect published result state back to the finished asset list.
|
||||
- [x] 5.6 Support batch submission of multiple campaign drafts with progress, cancellation, and per-draft result summaries.
|
||||
- [x] 5.7 Support immediate and scheduled publish for channels whose CLI wrapper supports `--schedule`.
|
||||
- [x] 5.8 Preserve source asset selection after partial failure so failed targets can be retried without recreating the draft.
|
||||
|
||||
## 6. Verification
|
||||
- [x] 6.1 Add unit tests for asset aggregation and capability validation.
|
||||
- [x] 6.2 Add runtime bridge tests with mocked `sau` command execution.
|
||||
- [x] 6.3 Add UI tests for navigation, account status rendering, composer validation, and job status transitions.
|
||||
- [x] 6.4 Run targeted typecheck/build for the affected web app.
|
||||
- [x] 6.5 Add UI tests for campaign draft batch publishing, cancellation, and mixed success/failure rendering.
|
||||
Reference in New Issue
Block a user