Skip to content

User-facing GUI to create & manage own corpus groups (follow-up to #2134) #2141

Description

@JSv4

Summary

Give users a first-class in-app GUI to create and manage their own corpus groups (CorpusGroup), so cross-corpus retrieval no longer requires Django admin access.

Follow-up to #2134 (multi-corpus retrieval: CorpusGroup + search_across_corpora, closes #2056). That PR shipped the full backend — model, service, GraphQL, and the search_across_corpora agent tool — but the only way to create/manage a group today is Django admin (superuser-only). This issue adds a user-facing management surface for the groups a user owns.

Scope

  • Per-user, not superuser. Any authenticated user can create, edit, and delete the corpus groups they own, matching the existing permission model (mutations are @login_required + django-guardian CRUD; CorpusGroup uses the standard BaseOCModel creator/is_public/guardian visibility surface). No superuser gate, no instance-wide "see everyone's groups" behavior — that would require deliberately breaking the "superuser = normal user for data" invariant pinned by CorpusGroupAuthorizationInvariantsTestCase::test_superuser_has_no_blanket_data_access and is explicitly out of scope here.
  • A group's form manages: title, slug (auto-generated when blank), description, is_public, member corpora (multi-select, each must be READ-visible to the user), and the bound default orchestrator agent (optional).

Backend — already complete (no changes expected)

The GraphQL surface from #2134 is ready to consume as-is:

  • Queries: corpusGroups (relay DjangoFilterConnectionField, per-viewer visible) and corpusGroup (single node) — config/graphql/corpus_queries.py. Backed by CorpusGroupService.list_visible_groups.
  • Mutations: createCorpusGroup / updateCorpusGroup / deleteCorpusGroupconfig/graphql/corpus_group_mutations.py, payload { ok, message, corpusGroup }.
    • Note the set/clear idiom for the bound agent: send defaultAgentId to set, or clearDefaultAgent: true to unbind — never both, never an empty string (mirrors preferredLlm/clearPreferredLlm in GlobalAgentManagement).
    • updateCorpusGroup's corpusIds replaces membership when provided.
  • CorpusGroupType.corpora and CorpusGroupType.defaultAgent are already gated per-viewer (a private member corpus / private bound agent never leaks through a shared group).

If a "my groups" query turns out to need a tighter filter than the per-viewer corpusGroups connection (e.g. creator = me only), add it to CorpusGroupService — do not compose visibility inline (Tier-0 service-layer rule).

Frontend — greenfield

There is currently zero corpus-group code in frontend/src (no query, mutation, type, or component).

Recommended pattern: mirror frontend/src/components/admin/corpus_categories/CorpusCategoryManagement.tsx + its co-located graphql.ts:

  • New folder frontend/src/components/admin/corpus_groups/ (or a user-facing location if we decide it belongs outside /admin — see Open questions) with CorpusGroupManagement.tsx + graphql.ts, exported from frontend/src/components/admin/index.ts.
  • A route in frontend/src/App.tsx (admin route block ~L415–452).
  • List via useQuery on a relay connection; create/edit through a single shared Modal keyed on the editing row (null = create); delete via ConfirmModal.
  • Reuse: SharedSegments (CardSegment, PageHeader, ScrollableTableWrapper), @os-legal/ui Table/Modal/inputs, react-toastify, and useFacetedRelayList for the group list / member-corpora pagination.
  • Member-corpora multi-select and the default-agent picker: reuse the existing corpus-search and agent-selection components already in the tree.

Open questions

  • Placement/entry point. Where does a user reach this? Options: a "Corpus Groups" item in the user's own area (most consistent with per-user ownership), or a card on an existing settings surface. GlobalSettingsPanel's settingsItems grid is superuser-gated, so it's the wrong home for a per-user feature as-is.
  • Should managing a group's membership live here, or also inline from each corpus ("add this corpus to a group")? Membership-here is the MVP.

Acceptance criteria

  • A logged-in user can create a corpus group (title + optional slug/description/visibility, member corpora, optional default agent) from the GUI.
  • They can edit and delete groups they own; groups they don't own are not listed and not editable.
  • Member-corpus and default-agent pickers only offer items the user can READ.
  • Component tests (.ct.tsx) covering create/edit/delete happy paths + the empty state, following the project's component-test wrapper conventions, with a doc screenshot.
  • Once the GUI lands, remove the Django admin CorpusGroupAdmin registration (opencontractserver/corpuses/admin.py) so there's a single management surface (no-dead-code), unless we deliberately keep it as a superuser backstop.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions