Skip to content

feat: workspace owners can permanently delete a channel - #257

Open
sercada wants to merge 1 commit into
openclaw:mainfrom
sercada:feat/delete-channels
Open

sercada wants to merge 1 commit into
openclaw:mainfrom
sercada:feat/delete-channels

Conversation

@sercada

@sercada sercada commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor
Maintainer edits

MUST: Keep Allow edits from maintainers enabled for this PR so maintainers
can help update the branch when needed.

What Problem This Solves

Workspace owners can archive a channel but cannot remove a channel they no longer
want, together with its messages and files.

User Impact

User impact: owners can delete a channel from Channel settings → Delete
channel...
, from the API, or with clickclack channels delete. The dialog
shows what will be removed (messages, thread replies, pins, topics, files and
their size), offers to archive instead, and enables deletion only after the
channel name is typed. Deletion is permanent. People viewing the channel are
moved to their fallback conversation with a notice. The server refuses to
delete a workspace's last channel and the Guests workspace's provisioned
channels. On SQLite, two new migrations make deleting many messages fast: a
10k-message channel in a 120k-message workspace now deletes in 0.3 s instead of
6.6 minutes. Existing workspace deletion benefits the same way.

Why This Change Was Made

  • Deletion reuses the existing foreign-key cascade in one transaction, so the
    channel, messages, replies, reactions, pins, channel topics, read pointers,
    and notification settings go together. Uploads attached only to that channel
    are removed and their objects go through the durable cleanup queue already
    used for workspace deletion; uploads still used elsewhere and the workspace
    icon are kept. The audit log records the channel and the removed counts.
  • On SQLite, every row removed by the cascade did two full scans: one of
    messages to find replies and quotes (parent_message_id and
    quoted_message_id had no index), and one of the FTS5 search index, because
    the delete trigger matched the unindexed messages_fts.message_id. Deleting
    a channel therefore grew with channel size × workspace size. New partial
    indexes cover the child keys on both stores, and message_search_rows records
    each message's FTS rowid so the search triggers update rows by rowid. Both
    are needed; either alone leaves most of the cost.
  • A workspace-scoped channel.deleted event tells clients to leave the channel.
    Earlier events for the channel stay in the log, so replay cursors remain valid;
    realtime delivery already skips events whose channel no longer exists.
  • On PostgreSQL, a deletion locks the workspace row (FOR NO KEY UPDATE) before
    it counts or selects anything, in the same workspace-first order as workspace
    deletion and updates. Competing deletions cannot both pass the last-channel
    check, and the workspace icon cannot move onto an upload being removed; event
    appends and inserts, which take KEY SHARE, are not blocked. Candidate uploads
    are then locked FOR UPDATE and re-checked, so an attachment elsewhere either
    commits first (and the upload is kept) or waits and fails its foreign key.
  • Only human owners can delete. Moderators, members, and bot tokens get 403;
    blocked deletions return 409 with a blocker code the web app explains.
  • clickclack channels delete deletes only a channel named with --channel on
    its own command line (global or subcommand form). CLICKCLACK_CHANNEL and the
    saved default channel pick the channel for everyday commands, so they never
    choose what gets deleted. Without --yes the command prints the counts and
    exits.

The new migrations (sqlite/0043, sqlite/0044, postgres/0036) share numbers
with the agent question migrations in #258. Migrations apply by file name, so both
work together; whichever PR lands second can renumber to keep the sequence tidy.

Evidence

Settings danger zone Confirmation with preview Another member when it is deleted
settings confirm watcher

The CLI with a default channel in the environment, real server and CLI from
the same build:

Previous head `aa459dd5`: channels delete --yes deleted #launch from CLICKCLACK_CHANNEL
# previous-head-aa459dd5: workspace with #launch and #old-launch (2 messages); the shell exports CLICKCLACK_CHANNEL=launch
channels: launch old-launch
$ clickclack channels delete --yes
  deleted #launch: 0 messages, 0 thread replies, 0 files
  exit=0
channels: old-launch
$ clickclack channels delete --channel old-launch
  cannot delete #old-launch: 2 messages, 0 thread replies, 0 files (last_channel)
  exit=1
$ clickclack channels delete --channel old-launch --yes
  cannot delete #old-launch: 2 messages, 0 thread replies, 0 files (last_channel)
  exit=1
channels: old-launch
This head: the default channel is ignored; the named channel is deleted after the preview
# branch: workspace with #launch and #old-launch (2 messages); the shell exports CLICKCLACK_CHANNEL=launch
channels: launch old-launch
$ clickclack channels delete --yes
  usage: clickclack channels delete --channel CHANNEL --yes
  exit=1
channels: launch old-launch
$ clickclack channels delete --channel old-launch
  refusing to permanently delete #old-launch: 2 messages, 0 thread replies, 0 files without --yes
  exit=1
$ clickclack channels delete --channel old-launch --yes
  deleted #old-launch: 2 messages, 0 thread replies, 0 files
  exit=0
channels: launch

Tests:

  • Shared store suite on SQLite and Postgres (channeldeletiontest): the
    channel's messages, topics, search results, and exclusive upload are removed
    while other channels, shared uploads, and the workspace icon survive; the
    deletion counts match the preview; a replay cursor pointing at one of the
    channel's old events still replays forward to channel.deleted; guard rails
    for the last channel, provisioned Guests channels, archived channels, and
    non-owners.
  • TestCascadeChildKeysUseIndexes (SQLite query plan) and
    TestCascadeChildKeyIndexesExist (Postgres).
  • PostgreSQL interleavings, held open with real locks instead of timing:
    TestDeleteChannelSerializesLastChannelDecisions runs two deletions of a
    workspace's last two channels (one succeeds, one gets last_channel), and
    TestDeleteChannelKeepsUploadsAttachedDuringDeletion attaches the channel's
    only upload elsewhere while the deletion is paused after choosing it (the
    attachment either keeps the upload or fails). Both pass three times in a row;
    against the previous revision they fail with deleted=2 blocked=0 remaining=0
    and attachment succeeded but kept 0 rows.
  • TestSearchTriggersFindRowsThroughTheRecordedRowid: upgrades a database with
    an existing message, detaches the search rows from their message IDs, then
    edits one message and deletes the other's channel; both rows are still
    replaced or removed and search returns the edited message. With the old
    message_id triggers it fails with detached rows = 2.
  • TestChannelDeletionHTTP: 403 for members and bot tokens, 404 for
    unknown or already deleted channels, 204 for the owner, the live and
    replayed channel.deleted event on a member's socket, the audit entry, and
    the last_channel blocker with 409.
  • TestChannelsDeleteRequiresExplicitConfirmation: the CLI prints the preview
    and refuses without --yes.
  • TestChannelsDeleteIgnoresDefaultChannels runs the CLI entry point with
    CLICKCLACK_CHANNEL, then with a saved default channel: channels delete --yes
    sends no DELETE, while --channel in either flag position deletes. Without the
    fix it fails with CLICKCLACK_CHANNEL chose the channel to delete.
  • tests/e2e/channel-deletion.spec.ts: an owner deletes after reviewing the
    preview and typing the name; a member watching the channel is moved out with
    a notice; the last channel cannot be deleted.
  • apps/web/src/lib/channel-deletion.test.ts: name confirmation, blocker
    messages, and the notice text.
  • Local CI steps: pnpm fmt:check, pnpm lint, pnpm typecheck,
    pnpm -r typecheck, web unit tests, pnpm docs:site, and go test ./...
    with CLICKCLACK_POSTGRES_TEST_DSN, deadcode, and the embedded build is
    current and repeatable (coverage gate 86.8%). On this machine these tests also
    fail on unchanged main: TestHTTPBodyDeadlineStillBoundsStalledRequestBodies,
    uploadstore TestR2HeaderNetworkLifecycle/progressing_PUT, and intermittently
    TestHTTPErrorPathsAndSPA (1 of 4 runs on main), and
    TestHTTPSlashCommandRequiresChannelWriteAuthorityBeforeCallback (1 of 5 runs
    on main, 5 of 5 pass on this branch). The first CI run's Playwright job
    failed once in chat.spec.ts › clicking the active conversation does not refetch its messages; locally that test passes 3 of 3 alone and the whole
    chat.spec.ts plus channel-deletion.spec.ts pass 42 of 42 with two workers.

Deleting a channel on SQLite (Python sqlite3 3.45 applying the real
migrations; 120,000 messages in 12 channels; the deleted channel has 7,000
roots, 2,500 thread replies, and 500 quote replies):

Schema Delete time
main 395.4 s
child-key indexes only 220.3 s
this PR (indexes and search row map) 0.29 s

Applying this PR's migrations to that populated database took 0.14 s. A
smaller run (30,000 messages, 3,000 deleted) shows the two fixes are
independent: 27.7 s on main, 11.5 s with only the search row map, 17.7 s with
only the indexes, and 0.10 s with both.

AI-assisted: prepared with Claude Code; I reviewed the change and the evidence.

🤖 Generated with Claude Code

@sercada
sercada requested a review from a team as a code owner September 13, 2026 12:33
@clawsweeper

clawsweeper Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Sep 13, 2026
@clawsweeper

clawsweeper Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: blocked before merge.

What this changes

Adds permanent channel deletion for human workspace owners through the browser, API, SDK, and CLI, with content previews, file cleanup, viewer notices, and faster database cascades.

Example: An owner removes #old-launch containing two messages

  • Before: The owner can archive #old-launch while retaining its history.
  • After: Running clickclack channels delete --channel old-launch --yes removes the channel and its two messages while #launch remains.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) Demonstrated functionality is useful, but unresolved client defects, upgrade verification, and product direction limit readiness.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The matching real server/CLI transcript demonstrates explicit-target deletion and confirmation refusal, while inspected browser images show the preview and watcher fallback. Ordinary behavior proof is sufficient; stored-model compatibility remains insufficient because the SQL benchmark and focused migration test do not demonstrate a populated latest-release server upgrade with existing settings and data intact.
Patch quality 🦐 gold shrimp (3/6) 2 actionable review findings remain.

Product

Kind: Feature · Worth it: Needs a maintainer decision
User problem: Workspace owners cannot permanently remove an unwanted channel together with its history and files.
Reason: Archive preserves history by design; permanent deletion is a distinct useful capability. Its irreversible lifecycle contract needs an area-owner decision.

Merge readiness

⛔ Blocked before merge - 5 items remain

This PR provides useful functionality absent from current main and merits continued review; the two previously reported client defects remain unresolved.

Priority: P2
Reviewed head: c6d1ef841098ed1874b460bde5e5ba62fe0568d1
Owner decision: Required. See Decision needed.

Decision needed

  • Question: Should human workspace owners gain irreversible channel deletion through the browser, API, SDK, and CLI alongside reversible archival?
  • Recommendation: Approve owner-only permanent deletion: Offer deliberate content removal with previews, explicit confirmation, and protected-channel rules after the implementation gates are cleared.
  • Why: This adds a destructive public capability and permission contract; the inspected discussion contains no area-owner approval of that direction.

Before merge

  • Retire pending channel-list reads before removing the channel (P2) - A channel.created or channel.updated refresh can capture the list before deletion and arrive after channel.deleted. This filter leaves channelsLoadSerial unchanged, so loadChannels accepts that snapshot and restores the deleted sidebar entry. For a nonselected channel, this path returns without navigation or a replacement load. Invalidate or reconcile pending list reads before applying deletion. This remains the previously reported late discovery on the unchanged reviewed head.
  • Prevent pending searches from restoring deleted channel content (P2) - An initial search or Load more response captured before deletion can arrive after this filter and restore the deleted channel's messages. Deletion leaves searchRequestID unchanged, so both response paths still admit it; ordinary fallback navigation does not reset the search session. Retire affected requests or reconcile results against terminal deletion, including pagination. This remains the previously reported late discovery on the unchanged reviewed head.
  • Complete next step - Resolve conflicts with current main and refresh review against the integrated source and regenerated browser assets.
  • Resolve maintainer decision - Resolve the maintainer decision shown above before merge.
  • Add data-model compatibility proof - The review found that existing stored data may not work after upgrade. Show that existing data still loads and works with this change.

Findings

  • [P2] Retire pending channel-list reads before removing the channel — apps/web/src/ChatApp.svelte:595-596
  • [P2] Prevent pending searches from restoring deleted channel content — apps/web/src/ChatApp.svelte:600-604

Tests

  • Low-value test apps/web/src/lib/channel-deletion.test.ts: channelDeletedNotice names the actor when known: Remove this helper-only test that pins incidental notice wording; the browser deletion scenario already verifies the visible notice.
  • Missing end-to-end proof: Demonstrate delayed channel-list and initial/paginated search responses across deletion, plus a populated v0.7.0-to-integrated-head server upgrade preserving existing settings, messages, uploads, and working search.
Agent review details

How this fits together

ClickClack stores workspace conversations in SQLite or PostgreSQL and serves browser and API clients. Channel deletion checks ownership, removes content transactionally, queues file cleanup, and notifies connected clients.

flowchart TD
  A[Owner uses browser or CLI] --> B[Channel deletion API]
  B --> C[Check human owner and protected channels]
  C --> D[Database removes channel content]
  D --> E[Durable file cleanup queue]
  D --> F[Workspace deletion event]
  F --> G[Clients remove channel and move viewers]
Loading

Technical review

Best possible solution:

Reuse transactional cascades and durable cleanup, make deletion terminal for client reads, and validate the owner-approved operation on existing databases.

Do we have a high-confidence way to reproduce the issue?

Yes for the introduced races: hold a channel-list or search response captured before deletion, process channel.deleted, then release it. Source shows that the response remains admissible; no reviewer-side failing run was executed.

Is this the best way to solve the issue?

Unclear overall: the existing transaction and cleanup owners fit the operation, but terminal client-read handling, populated upgrade verification, and product acceptance remain incomplete.

Full review comments:

  • [P2] Retire pending channel-list reads before removing the channel — apps/web/src/ChatApp.svelte:595-596
    A channel.created or channel.updated refresh can capture the list before deletion and arrive after channel.deleted. This filter leaves channelsLoadSerial unchanged, so loadChannels accepts that snapshot and restores the deleted sidebar entry. For a nonselected channel, this path returns without navigation or a replacement load. Invalidate or reconcile pending list reads before applying deletion. This remains the previously reported late discovery on the unchanged reviewed head.
    Confidence: 0.98
    Late finding: first raised on code an earlier review cycle already covered.
  • [P2] Prevent pending searches from restoring deleted channel content — apps/web/src/ChatApp.svelte:600-604
    An initial search or Load more response captured before deletion can arrive after this filter and restore the deleted channel's messages. Deletion leaves searchRequestID unchanged, so both response paths still admit it; ordinary fallback navigation does not reset the search session. Retire affected requests or reconcile results against terminal deletion, including pagination. This remains the previously reported late discovery on the unchanged reviewed head.
    Confidence: 0.97
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 1169fb099659.

Provenance checked

  • apps/api/internal/store/sqlite/migrations/0044_message_search_rows.sql keeps the original intent (cff5fb0: Scope SQLite search inside FTS while keeping ordinary message bodies synchronized.)
  • apps/web/src/ChatApp.svelte: clearComposerNoticeFor keeps the original intent (feat: add web command menus and slash dispatch #82: Keep slash-command responses local and dismissible and prevent delayed callbacks from updating another conversation.)
  • apps/web/src/components/settings/ChannelSettingsModal.svelte keeps the original intent (fix: add channel archive controls for managers #153: Expose archive and restore controls while retaining history and capability-based API authorization.)
  • apps/web/src/lib/uploads.ts: formatBytes keeps the original intent (refactor: split web chat structure #3: Extract chat helpers and components without changing behavior while retaining state ownership and mutations in ChatApp.)
  • apps/api/cmd/clickclack/client_commands.go: channels keeps the original intent (a9f86a2: Add an agent-friendly chat client.)
  • apps/api/internal/webassets/dist keeps the original intent (fix(build): preserve generated JavaScript literal whitespace #255: Preserve compiler output because rewriting JavaScript literal whitespace corrupts browser behavior.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

  • Applicable repository policy: Read the full root AGENTS.md. No nested AGENTS.md or maintainer-notes directory was found; SQL query and schema changes accompany generated bindings. (AGENTS.md:3, c6d1ef841098)
  • Capability remains absent from main: Current-main handlers, stores, browser source, protocol, SDK, and docs contain no permanent channel deletion capability. The canonical search established no merged fixing or replacement PR. (apps/api/internal/httpapi/channels.go:10, 1169fb099659)
  • Real CLI behavior: The complete supplied body, sourceRevision a0a4b99a2a0899788d93a0c8651d201868d49d4018e31bbe66af91425fbdc847, records a matching real server and CLI: environment defaults cannot select the deletion target, missing confirmation is refused, and confirmed deletion removes #old-launch and two messages while #launch survives. (apps/api/cmd/clickclack/client_commands.go:164, c6d1ef841098)
  • Inspected browser proof: Viewed the three downloaded channel-deletion screenshots listed in the media manifest. They show #asia-sales settings, a preview of four messages and one 13 KB file, disabled incomplete confirmation, and a member moved to #general with a deletion notice.
  • Prior findings remain on unchanged head: The earlier completed review inspected the identical head. Deletion filters current arrays without retiring channel-list or search requests; their response admission checks still accept snapshots captured before deletion. (apps/web/src/ChatApp.svelte:594, c6d1ef841098)
  • Transactional authorization and cleanup: The HTTP handler rejects bot tokens; both stores check current workspace ownership and moderation inside the transaction before deletion and cleanup insertion. PostgreSQL also locks the workspace and candidate uploads. No concrete introduced authorization bypass was established. (apps/api/internal/store/postgres/channel_deletion.go:59, c6d1ef841098)

Review metrics

Metric Value Why it matters
Source growth production +1,338 net LOC; tests +1,165; generated bindings +492; docs +59 The growth implements a cross-client destructive operation and cascade-performance repair whose value depends on product acceptance.

Labels

Label changes:

No label changes.

Label justifications:

  • P2: Permanent channel removal is a bounded improvement, with pre-merge client defects rather than an urgent shipped regression.
  • merge-risk: 🚨 compatibility: The SQLite migration replaces persistent search synchronization, and populated latest-release upgrade compatibility remains unverified.
  • merge-risk: 🚨 session-state: Pending channel-list and search responses can restore deleted entries in client state.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.
  • proof: sufficient: Contributor real behavior proof is sufficient.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only; repair and merge need explicit maintainer commands such as @clawsweeper autofix or @clawsweeper automerge.

History

Review history (14 earlier review cycles; latest 8 shown)
  • reviewed 2026-10-07T15:43:37.610Z sha c6d1ef8 :: blocked before merge. :: none
  • reviewed 2026-10-07T17:53:25.683Z sha c6d1ef8 :: blocked before merge. :: none
  • reviewed 2026-10-07T20:50:14.395Z sha c6d1ef8 :: blocked before merge. :: none
  • reviewed 2026-10-08T00:05:32.467Z sha c6d1ef8 :: blocked before merge. :: none
  • reviewed 2026-10-08T10:36:07.930Z sha c6d1ef8 :: blocked before merge. :: none
  • reviewed 2026-10-08T12:05:58.671Z sha c6d1ef8 :: blocked before merge. :: [P2] Retire pending channel-list reads before removing the channel | [P2] Prevent pending searches from restoring deleted channel content
  • reviewed 2026-10-08T15:53:10.445Z sha c6d1ef8 :: blocked before merge. :: [P2] Retire pending channel-list reads before removing the channel | [P2] Prevent pending searches from restoring deleted channel content
  • reviewed 2026-10-08T21:04:41.886Z sha c6d1ef8 :: blocked before merge. :: [P2] Retire pending channel-list reads before removing the channel | [P2] Prevent pending searches from restoring deleted channel content

Reviewed October 8, 2026, 11:40 PM ET / October 9, 2026, 03:40 UTC (Revision 15).

@sercada
sercada force-pushed the feat/delete-channels branch from b6d6079 to aa459dd Compare September 13, 2026 12:57
Owners delete a channel from Channel settings, the API, or the CLI after
reviewing what will be removed and typing the channel name. The channel's
messages, replies, reactions, pins, topics, read state, and exclusive uploads
go in one transaction; a workspace-scoped channel.deleted event moves viewers
out. The last channel and the Guests workspace's provisioned channels cannot
be deleted. The CLI deletes only a channel named with --channel on its command
line, never CLICKCLACK_CHANNEL or the saved default.

On SQLite, cascading many message deletions scanned both the messages table
and the FTS index once per row. Child-key indexes and a message-to-search-row
map make a 10k-message channel in a 120k-message workspace delete in 0.3 s
instead of 6.6 minutes, which also speeds up workspace deletion.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@sercada
sercada force-pushed the feat/delete-channels branch from aa459dd to c6d1ef8 Compare September 13, 2026 13:27
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Sep 13, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Oct 8, 2026

This branch has not been deployed

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

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant