feat: custom field designer and presence #13

Merged
KofTwentyTwo merged 7 commits from feature/presence-custom-fields into develop 2026-10-14 12:09:22 +00:00
KofTwentyTwo commented 2026-10-14 02:10:05 +00:00 (Migrated from github.com)

Summary

Phases (f) and (g), qbit side, of QRun-IO/qqq#1136 (spec §4 rows 8 and 9, §6.3, §9.2): the custom field designer and presence. Stacked on #8. It builds against the QQQ develop SNAPSHOT and moves to RC.6 together with #8, so it stays a draft until then.

Custom fields:

  • Tables: collaboration_field_definition ("Custom Fields", audited) with a classification (PUBLIC, CONFIDENTIAL, PII or PHI), plus collaboration_field_value, the EAV fallback.
  • CustomFieldsProvider, a RuntimeMetaDataProviderInterface, contributes each ACTIVE definition as a normal field cf_<name>:
    • type, label, required, help text, section, search field, and the AiFieldMetaData classification;
    • stored with JsonPathStorage("qqq_custom_fields", name) on PostgreSQL and the memory backend, after a one-time check that the column exists;
    • on other backends, a read-only EAV virtual field that can be filtered through a criteria expander.
  • Actions: Publish field, Archive field (its Review counts the affected values) and Rebuild index. Type changes are allowed only in DRAFT.
  • Indexes: collaborationBuildFieldIndexes creates expression indexes with CREATE INDEX ... CONCURRENTLY, tracking BUILDING, READY or FAILED. With withManagedIndexes(false) it only records the DDL.
  • CollaborationSchema.customFieldsDdl(table) prints the one-time column and GIN index.

Presence:

  • PresenceRegistry is in memory and replicated across pods over the core realtime bus, on the internal instance:presence channel. Entries expire after 45 s, are capped at 50,000, and one user's tabs are merged.
  • presence.changed is published to record: and table: channels.
  • Endpoints: POST, DELETE and GET /qqq/v1/qbit/collaboration/presence. Each re-checks access and needs PRESENCE enabled.
  • Nothing is persisted, as the owner decided. Admins get a read-only "Who's here" widget.

Limitations:

  • EAV fields are read-only for now.
  • Multi-select uses JSON arrays without native array operators.
  • Other pods pick up definition changes through the overlay's generation refresh.

Test plan

  • ./mvnw -o verify against a fresh develop SNAPSHOT: 113 tests, checkstyle clean, coverage 96.1% instructions and 99.1% classes.
  • CircleCI on RC.6.

🤖 Generated with Claude Code

## Summary Phases (f) and (g), qbit side, of QRun-IO/qqq#1136 (spec §4 rows 8 and 9, §6.3, §9.2): the custom field designer and presence. **Stacked on #8.** It builds against the QQQ develop SNAPSHOT and moves to RC.6 together with #8, so it stays a draft until then. **Custom fields:** - **Tables:** `collaboration_field_definition` ("Custom Fields", audited) with a `classification` (PUBLIC, CONFIDENTIAL, PII or PHI), plus `collaboration_field_value`, the EAV fallback. - **`CustomFieldsProvider`**, a `RuntimeMetaDataProviderInterface`, contributes each ACTIVE definition as a normal field `cf_<name>`: - type, label, required, help text, section, search field, and the `AiFieldMetaData` classification; - stored with `JsonPathStorage("qqq_custom_fields", name)` on PostgreSQL and the memory backend, after a one-time check that the column exists; - on other backends, a read-only EAV virtual field that can be filtered through a criteria expander. - **Actions:** **Publish field**, **Archive field** (its Review counts the affected values) and **Rebuild index**. Type changes are allowed only in DRAFT. - **Indexes:** `collaborationBuildFieldIndexes` creates expression indexes with `CREATE INDEX ... CONCURRENTLY`, tracking BUILDING, READY or FAILED. With `withManagedIndexes(false)` it only records the DDL. - `CollaborationSchema.customFieldsDdl(table)` prints the one-time column and GIN index. **Presence:** - `PresenceRegistry` is in memory and replicated across pods over the core realtime bus, on the internal `instance:presence` channel. Entries expire after 45 s, are capped at 50,000, and one user's tabs are merged. - `presence.changed` is published to `record:` and `table:` channels. - Endpoints: `POST`, `DELETE` and `GET /qqq/v1/qbit/collaboration/presence`. Each re-checks access and needs PRESENCE enabled. - Nothing is persisted, as the owner decided. Admins get a read-only "Who's here" widget. **Limitations:** - EAV fields are read-only for now. - Multi-select uses JSON arrays without native array operators. - Other pods pick up definition changes through the overlay's generation refresh. ## Test plan - [x] `./mvnw -o verify` against a fresh develop SNAPSHOT: 113 tests, checkstyle clean, coverage 96.1% instructions and 99.1% classes. - [ ] CircleCI on RC.6. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.