Skip to content

Slice 5: Tags + filtering - #4

Merged
DanMat merged 1 commit into
mainfrom
feat/tags
Sep 4, 2026
Merged

DanMat merged 1 commit into
mainfrom
feat/tags

Conversation

@DanMat

@DanMat DanMat commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

The final CRM slice: tags — a normalized, polymorphic labelling system you apply to any contact, organization or deal, then filter by. Completes the CRM's core entities.

What's here

  • Schema — crm_tag + crm_taggable (005_tags). Normalized, not a JSON column, so "every contact tagged X" is a real indexed query. Tag name unique (case-insensitive); a (tag, subject) link is unique so tagging twice is a no-op.
  • Tags service — allow-listed and existence-checked subject type (no dangling links, never interpolated); unique names; findOrCreate; idempotent attach/detach; tagsFor / idsFor (the filter); clearFor (called from each entity's delete); deleting a tag clears its links but keeps the subjects (transaction).
  • Cross-entity delete — deleting a contact/org/deal now also clears its tag links, so a "forget" leaves no residue.
  • MCP — crm_tags / _create / _delete / _attach / _detach / _tags_for / _tagged (the last resolves "all contacts tagged X" to full records), all on the wildcard-immune nimbuscms.crm capability.
  • Admin — a Tags management page (list with usage counts, create/rename/delete); a reusable tag block (removable chips + add-existing-or-type-new) on every record edit page; and a tag filter bar on every list/board that filters to "everything tagged X". Shared tag-attach/tag-detach actions across crm/crm-organizations/crm-deals (each inheriting crm:write + CSRF).
  • Guide — tags section.

Tests

  • New: TagsTest (unique names, case-insensitive find-or-create, usage counts, idempotent + validated attach, subject/tag existence, cross-type, detach, delete-clears-links-keeps-subjects, clearFor).
  • New: TagsAdminTest (management page escapes a hostile name; block escapes + carries CSRF/subject; filter bar marks active + hides unused).
  • Extended: Contacts/Organizations/Deals tests (delete clears tag links); CrmToolsetTest (tag tools listed, content token can't reach them, attach-by-name → tagged resolves records, cross-type usage, delete-removes-everywhere). All setups build + truncate the tag tables.

Security posture

Every tag surface gates on the wildcard-immune nimbuscms.crm capability. Subject type is bound + allow-listed + existence-checked; tag links can't dangle. Store-raw/escape-on-render (XSS tests on the page, block and chips). Delete leaves no residue.

cs-fixer + PHPStan level 6 green locally (borrowed-vendor); phpunit runs in CI.

With this, the CRM's core entities — contacts, organizations, activities, deals and tags — are complete.

🤖 Generated with Claude Code

Adds normalized, polymorphic tags — the last CRM slice.

- Schema: crm_tag + crm_taggable (005_tags). Normalized rather than a
  JSON column so "every contact tagged X" is a real indexed query.
  Tag name unique (case-insensitive); a (tag, subject) link is unique so
  tagging twice is a no-op.
- Tags service: allow-listed + existence-checked subject type (no
  dangling links, never interpolated); unique names; findOrCreate;
  idempotent attach/detach; tagsFor / idsFor (the filter); clearFor
  (called from each entity's delete); delete-a-tag clears links but keeps
  subjects (transaction).
- Cross-entity delete: deleting a contact/org/deal now also clears its
  tag links.
- MCP: crm_tags / _create / _delete / _attach / _detach / _tags_for /
  _tagged (the last resolves "all contacts tagged X" to full records),
  all on the wildcard-immune capability.
- Admin: a Tags management page (list with usage counts, create/rename/
  delete); a reusable tag block (removable chips + add-existing-or-new)
  on every record edit page; and a tag filter bar on every list/board
  that filters to "everything tagged X". Shared tag-attach/tag-detach
  actions across crm/crm-organizations/crm-deals.
- Guide: tags section.
- Tests: TagsTest, TagsAdminTest; per-entity tag-link-cleared-on-delete;
  CrmToolset tag tools + gating + cross-type + resolve; all setups build
  + truncate the tag tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DanMat
DanMat merged commit 454d585 into main Sep 4, 2026
2 checks passed
@DanMat
DanMat deleted the feat/tags branch September 4, 2026 21:14
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.

1 participant