Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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.Tagsservice — allow-listed and existence-checked subject type (no dangling links, never interpolated); unique names;findOrCreate; idempotentattach/detach;tagsFor/idsFor(the filter);clearFor(called from each entity's delete); deleting a tag clears its links but keeps the subjects (transaction).crm_tags/_create/_delete/_attach/_detach/_tags_for/_tagged(the last resolves "all contacts tagged X" to full records), all on the wildcard-immunenimbuscms.crmcapability.tag-attach/tag-detachactions acrosscrm/crm-organizations/crm-deals(each inheritingcrm:write+ CSRF).Tests
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).TagsAdminTest(management page escapes a hostile name; block escapes + carries CSRF/subject; filter bar marks active + hides unused).Contacts/Organizations/Dealstests (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.crmcapability. 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