Skip to content

feat: align opportunity view actions with people and companies#138

Merged
ManukMinasyan merged 1 commit intomainfrom
feat/opportunity-view-actions-consistency
Feb 20, 2026
Merged

feat: align opportunity view actions with people and companies#138
ManukMinasyan merged 1 commit intomainfrom
feat/opportunity-view-actions-consistency

Conversation

@ManukMinasyan
Copy link
Copy Markdown
Contributor

Move Edit button outside the dropdown as a standalone action and add Copy Page URL / Copy Record ID actions inside the dropdown, matching the existing pattern in ViewPeople and ViewCompany.

Move Edit button outside the dropdown as a standalone action and add
Copy Page URL / Copy Record ID actions inside the dropdown, matching
the existing pattern in ViewPeople and ViewCompany.
Copilot AI review requested due to automatic review settings February 20, 2026 03:27
@ManukMinasyan ManukMinasyan merged commit b401401 into main Feb 20, 2026
11 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns the Opportunity “view” page header actions with the existing People/Company pattern by surfacing “Edit” as a standalone action and adding convenience “copy” actions in the dropdown.

Changes:

  • Moves EditAction out of the header dropdown and into the primary header actions.
  • Adds “Copy page URL” and “Copy record ID” actions to the header dropdown for Opportunities.

Comment on lines +39 to +43
$this->js("
navigator.clipboard.writeText('{$url}').then(() => {
new FilamentNotification()
.title('URL copied to clipboard')
.success()
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navigator.clipboard.writeText(...) can fail (permissions, insecure context, unsupported browser). Add a .catch(...) branch (and ideally a danger notification) so the UI doesn’t silently fail and leave an unhandled promise rejection in the console.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +57
$this->js("
navigator.clipboard.writeText('{$id}').then(() => {
new FilamentNotification()
.title('Record ID copied to clipboard')
.success()
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: this clipboard write has no failure handling. Add a .catch(...) branch and user-facing notification for the error case.

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 32
protected function getHeaderActions(): array
{
return [
GenerateRecordSummaryAction::make(),
EditAction::make()->icon('heroicon-o-pencil-square')->label('Edit'),
ActionGroup::make([
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes the header action layout and introduces new header actions (copyPageUrl, copyRecordId). Consider adding/adjusting a Pest test for ViewOpportunity to assert these actions are present (and that Edit remains available) to prevent regressions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +33 to +37
ActionGroup::make([
Action::make('copyPageUrl')
->label('Copy page URL')
->icon('heroicon-o-clipboard-document')
->action(function (Opportunity $record): void {
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Copy page URL / Copy record ID” action definitions are now duplicated across ViewPeople, ViewCompany, and ViewOpportunity. Consider extracting these into a shared helper (e.g., a trait or a small factory method returning the ActionGroup) so fixes (icons, notifications, error handling) don’t have to be repeated in multiple pages.

Copilot uses AI. Check for mistakes.
ManukMinasyan added a commit that referenced this pull request Feb 20, 2026
* feat: align opportunity view actions with people and companies (#138)

Move Edit button outside the dropdown as a standalone action and add
Copy Page URL / Copy Record ID actions inside the dropdown, matching
the existing pattern in ViewPeople and ViewCompany.

* feat: add ValidatesCustomFields trait for API FormRequests

* feat: add custom field validation and write support to API

- Wire ValidatesCustomFields trait into all 10 FormRequests
- Extract and persist custom_fields in all Create/Update actions via TenantContextService
- Change loadMissing to load in controllers for fresh custom field values
- Add tests: create/update with custom fields, validation rejection, unknown fields ignored

* feat: force JSON responses for all API routes

* feat: add cursor pagination support to all list endpoints

* feat: install and configure Scramble for auto-generated API docs

* feat: add sparse fieldsets support to all list endpoints

* refactor: accept explicit perPage parameter in List actions

* feat: add custom fields metadata endpoint (GET /api/v1/custom-fields)

* fix: resolve arch test failures and use correct model imports

* fix: apply rector rules for type hints and return types

* feat: enable API tokens feature in Jetstream

* refactor: simplify MCP endpoint path from /mcp/relaticle to /mcp

* feat: rebuild API tokens page with Filament components

Replace raw Jetstream ApiTokenManager with custom Filament-based
Livewire components matching the Profile page pattern:

- CreateApiToken: form with token name (unique per user), required
  permissions, and modal displaying the new token with copy button
- ManageApiTokens: Filament table with edit permissions and delete
  actions, proper wildcard abilities display
- Fix rate-limited notification using missing translation keys
- Add show() includes support for all 5 API controllers

* style: fix pint formatting in API token components

* fix: update laravel/mcp to v0.5.9 and fix tool pagination

laravel/mcp v0.5.7 was missing the Server\Attributes directory
(Description, Name, Version, etc.), causing tools/list to crash
with "Attribute class not found" when clients tried to discover
tools. Also increased default pagination from 15 to 50 so all
20 CRM tools are returned in a single page.

* feat: migrate API resources to Laravel's JsonApiResource

Adopt JSON:API format for all API responses using Laravel 12's native
JsonApiResource. Resources now use toAttributes() and toRelationships()
instead of toArray(), and controllers no longer need manual include
parsing in show() methods since JsonApiResource handles ?include=
automatically via loadMissing().

* test: update API tests for JSON:API response format

Adapt all assertions to JSON:API structure: attributes nested under
data.attributes, type field at data level, relationships and included
for eager-loaded relations, and dot-notation plucks for collection data.

* refactor: remove redundant foreign keys and add include tests

Remove company_id/contact_id from resource attributes since they are
redundant with declared JSON:API relationships. Clean up stale docblock
on CustomFieldResource. Add include/relationship tests for all entities
with deeper assertions on included entry structure.

* feat: add team-scoped API tokens with expiration

API tokens are now permanently scoped to a specific team at creation
time, following the GitHub/Vercel pattern. This resolves issues where
MCP tools hang because $user->currentTeam is null or wrong for
programmatic clients.

- Add team_id foreign key to personal_access_tokens table
- Add team selector and expiration dropdown to token creation form
- Show team and expiration columns in token management table
- Resolve team from token first in SetApiTeamContext middleware
- Create custom PersonalAccessToken model with team relationship
ManukMinasyan added a commit that referenced this pull request Mar 29, 2026
* feat: add REST API and MCP server for CRM entities

Introduce a shared Actions layer for business logic (CRUD for companies,
people, opportunities, tasks, notes) consumed by both a versioned REST
API (Sanctum auth, rate limiting, team scoping) and a Laravel MCP server
with 20 tools, a schema resource, and overview prompt.

Includes SetApiTeamContext middleware that bridges Sanctum auth to the
web guard so observers and TeamScope work unchanged, API Resources with
custom field serialization, and 58 feature tests covering both surfaces.

Closes #85, closes #89

* fix: apply rector rules and fix arch test violations

- Add App\Mcp and App\Http\Resources to arch test ignore lists
  (MCP classes extend framework base classes, Resources extend JsonResource)
- Apply Rector rules: abort_unless(), query builder methods,
  arrow functions, class constants, removed unnecessary parentheses

* fix: eager load customField in CompanyObserver to prevent lazy loading violation

The dispatchFaviconFetchIfNeeded method loaded customFieldValues but not
the nested customField relationship. When getValue() accessed
customField->type, it triggered a LazyLoadingViolationException in
non-production environments.

* fix: API security hardening, query builder integration, and improved tests

- Fix middleware priority: SetApiTeamContext runs before SubstituteBindings
- Add Gate::authorize() to show() methods for defense-in-depth
- Scope exists validation rules to prevent cross-tenant references
- Remove duplicate throttle middleware from API routes
- Return 201 status for store() endpoints
- Integrate spatie/laravel-query-builder for declarative filtering/sorting
- Add sort column allowlists via allowedSorts()
- Update MCP tools for new List action signatures
- Improve tests with fluent AssertableJson, assertInvalid(), whereType()
- Add missing() assertions to verify no sensitive data leaks
- Add cross-tenant isolation tests for all 5 resources
- Simplify FormatsCustomFields with mapWithKeys()
- Remove dead class_exists guard in UpdateTask

* fix: resolve rector violations in api controllers and middleware

- Remove unnecessary parentheses around new Resource() calls in store methods
- Use explicit instanceof Team check in SetApiTeamContext middleware
- Add return type to arrow function in FormatsCustomFields trait

* API improvements: custom fields, pagination, docs, and hardening (#139)

* feat: align opportunity view actions with people and companies (#138)

Move Edit button outside the dropdown as a standalone action and add
Copy Page URL / Copy Record ID actions inside the dropdown, matching
the existing pattern in ViewPeople and ViewCompany.

* feat: add ValidatesCustomFields trait for API FormRequests

* feat: add custom field validation and write support to API

- Wire ValidatesCustomFields trait into all 10 FormRequests
- Extract and persist custom_fields in all Create/Update actions via TenantContextService
- Change loadMissing to load in controllers for fresh custom field values
- Add tests: create/update with custom fields, validation rejection, unknown fields ignored

* feat: force JSON responses for all API routes

* feat: add cursor pagination support to all list endpoints

* feat: install and configure Scramble for auto-generated API docs

* feat: add sparse fieldsets support to all list endpoints

* refactor: accept explicit perPage parameter in List actions

* feat: add custom fields metadata endpoint (GET /api/v1/custom-fields)

* fix: resolve arch test failures and use correct model imports

* fix: apply rector rules for type hints and return types

* feat: enable API tokens feature in Jetstream

* refactor: simplify MCP endpoint path from /mcp/relaticle to /mcp

* feat: rebuild API tokens page with Filament components

Replace raw Jetstream ApiTokenManager with custom Filament-based
Livewire components matching the Profile page pattern:

- CreateApiToken: form with token name (unique per user), required
  permissions, and modal displaying the new token with copy button
- ManageApiTokens: Filament table with edit permissions and delete
  actions, proper wildcard abilities display
- Fix rate-limited notification using missing translation keys
- Add show() includes support for all 5 API controllers

* style: fix pint formatting in API token components

* fix: update laravel/mcp to v0.5.9 and fix tool pagination

laravel/mcp v0.5.7 was missing the Server\Attributes directory
(Description, Name, Version, etc.), causing tools/list to crash
with "Attribute class not found" when clients tried to discover
tools. Also increased default pagination from 15 to 50 so all
20 CRM tools are returned in a single page.

* feat: migrate API resources to Laravel's JsonApiResource

Adopt JSON:API format for all API responses using Laravel 12's native
JsonApiResource. Resources now use toAttributes() and toRelationships()
instead of toArray(), and controllers no longer need manual include
parsing in show() methods since JsonApiResource handles ?include=
automatically via loadMissing().

* test: update API tests for JSON:API response format

Adapt all assertions to JSON:API structure: attributes nested under
data.attributes, type field at data level, relationships and included
for eager-loaded relations, and dot-notation plucks for collection data.

* refactor: remove redundant foreign keys and add include tests

Remove company_id/contact_id from resource attributes since they are
redundant with declared JSON:API relationships. Clean up stale docblock
on CustomFieldResource. Add include/relationship tests for all entities
with deeper assertions on included entry structure.

* feat: add team-scoped API tokens with expiration

API tokens are now permanently scoped to a specific team at creation
time, following the GitHub/Vercel pattern. This resolves issues where
MCP tools hang because $user->currentTeam is null or wrong for
programmatic clients.

- Add team_id foreign key to personal_access_tokens table
- Add team selector and expiration dropdown to token creation form
- Show team and expiration columns in token management table
- Resolve team from token first in SetApiTeamContext middleware
- Create custom PersonalAccessToken model with team relationship

* fix: address PR review findings across API layer

- Add terminate() to SetApiTeamContext for Octane-safe scope cleanup
- Validate entity_type in CustomFieldsController with Rule::in()
- Decouple UpdateTask from Filament panel context
- Scope ForceJsonResponse middleware to v1 routes only
- Use ??= in observers to prevent overwriting pre-set values
- Add $fillable to PersonalAccessToken, exclude from final_class rule
- Change CreationSource::API badge color from danger to info

* feat: add CreationSource::MCP enum value for AI agent interactions

Distinguish MCP-created records from REST API-created records by
introducing a dedicated CreationSource::MCP case. All 5 MCP create
tools now use this value instead of CreationSource::API.

* refactor: rename 'API Tokens' to 'Access Tokens' in UI labels

Tokens are used for both REST API and MCP server connections.
'Access Tokens' better reflects the broader scope, following
the pattern used by GitHub and Vercel.

* refactor: extract access token strings to translation file

Move all user-facing 'API Token' strings to lang/en/access-tokens.php
and rename to 'Access Tokens' throughout. Covers Filament page,
Livewire components, Blade templates, and user menu.

* fix: address remaining PR #136 review findings

- Clamp per_page to minimum of 1 in all List actions to prevent invalid pagination
- Use Js::from() for clipboard JS string escaping in View pages
- Fix entity_type 'person' to 'people' in CustomFieldsApiTest to match morph map
- Add PersonalAccessToken to arch test ignore list (Sanctum mocks prevent final)

* chore: replace scramble with scribe + scalar for API docs

* feat: configure scribe for scalar UI with response calls

* docs: add scribe @group annotations to API controllers

* feat: add custom scribe strategy for spatie query builder params

* feat: per-entity MCP schema resources with custom fields support

- Replace single CrmSchemaResource with per-entity resources
  (company, people, opportunity, task, note) that dynamically
  expose team-specific custom field definitions
- Add custom field validation to CreateCompanyTool and UpdateCompanyTool
  using shared ValidatesCustomFields trait with ValidationService
- Remove phantom columns (address, phone, country) from company tools
  that don't exist in the database schema
- Add ResolvesEntitySchema trait for consistent custom field resolution

* fix: improve scribe docs with response attributes and remove phantom fields

- Add #[ResponseFromApiResource] to store/show/update and #[Response(204)] to destroy on all controllers
- Remove phantom address/country/phone fields from Company request and resource
- Guard $this->user() in People/Opportunity requests for Scribe compatibility
- Configure Scribe beforeResponseCall in AppServiceProvider
- Limit ResponseCalls to GET-only, disable per-endpoint transactions
- Add generated Scribe views, OpenAPI spec, and Postman collection
- Change documentation route prefix from /documentation to /docs

* refactor: revert Scribe null-guards in FormRequests, use BodyParam attributes

Remove defensive $this->user() null-checks from People and Opportunity
FormRequests -- auth user always has a team in production. Instead, use
#[BodyParam] attributes on controller methods so Scribe discovers
parameters without needing to instantiate the FormRequest.

* chore: gitignore generated scribe files

Generated API docs (OpenAPI spec, Postman collection, Blade views,
Scribe tests) should be built on deploy, not committed to the repo.

* chore: remove tracked scribe generated files from git

* fix: remove scribe whitelist from storage/app/.gitignore

* docs: update API reference description, remove "coming soon"

* docs: replace API placeholder with live documentation guide

* docs: link API Reference directly to Scalar docs page

Remove the api-guide.md placeholder and link the docs hub card
directly to /docs/api (Scalar interactive docs). Support external
URLs in documentation config via a 'url' key.

* fix: resolve CI failures in arch tests, public pages, and task includes

- Add App\Scribe to arch test ignore lists (extends Strategy, not readonly)
- Update PublicPagesTest URLs from /documentation to /docs
- Skip url-only doc types in DocumentationService::search()
- Remove creator_id from TaskFactory, rely on observer like other factories
- Apply rector fixes (arrow return types, doesntContain, class constants)

* fix: add missing type hints to restore 99.9% type coverage

* fix: achieve 100% type coverage and raise CI threshold

* fix: guard against orphaned custom field values in API responses

* fix: remove token from PersonalAccessToken fillable and fix imports

* refactor: extract CustomFieldValidationService to eliminate duplication

Both the API FormRequest trait and the MCP tool trait had ~80 lines of
nearly identical custom field resolution and validation rule generation.
Extracted the shared logic into a dedicated service class that both
traits now delegate to with thin wrappers.

* fix: add custom fields support and FK validation to all MCP tools

Add ValidatesCustomFields trait and custom_fields schema parameter to
all 8 remaining MCP create/update tools (People, Opportunity, Task,
Note). Add team-scoped Rule::exists validation for company_id and
contact_id foreign keys in People and Opportunity tools.

* fix: wire MCP List tool schema params through to Spatie QueryBuilder

* fix: wrap create and update actions in DB transactions for data consistency

* fix: guard comment, forceFill removal, X-Team-Id validation, Scribe transact config

* fix: remove unused HasFactory trait from PersonalAccessToken

* fix: move saveCustomFields inside DB transaction in UpdateTask

UpdateTask was the only action that saved custom fields outside
the transaction, inconsistent with all other Create/Update actions.

* refactor: unify custom fields tenant context via middleware

Set TenantContextService::setTenantId() once in SetApiTeamContext
middleware instead of wrapping every action in withTenant(). Remove all
manual custom_fields extraction and saveCustomFields() calls from 10
action files — the UsesCustomFields trait handles this automatically
via model events (saving/saved).

* docs: add skill reference guides for Livewire, Pest, Flowforge, and Custom Fields

Introduce detailed developer reference guides under `.github/skills`:
- **Livewire Development**: Covers Livewire 4 components, reactive directives, JavaScript hooks, async actions, islands, and testing.
- **Pest Testing**: Documents best practices, browser testing, smoke tests, datasets, and architecture validation using Pest 4.
- **Flowforge Development**: Guides on building Kanban boards with drag-and-drop functionality, column/card configuration, and position management.
- **Custom Fields Development**: Details integration with Eloquent models, Filament resources, field types, validation, and feature flags.

These guides centralize key patterns and are organized for quick reference during development.

* fix: resolve PHPStan errors, HasMany type hints, and arch violations

- Fix Builder vs HasMany closure type hints in CustomFieldsController
  and ExecuteImportJob (runtime TypeError)
- Add readonly to CustomFieldValidationService (arch test)
- Add generic type annotations to List action return types
- Add @property annotations to CustomFieldResource for timestamps
- Remove unnecessary nullsafe operator in ResolvesEntitySchema
- Remove stale PHPStan ignore pattern for MCP attributes
- Skip Rector HasFactory rule for PersonalAccessToken

* feat: add MCP/API domain config, documentation, integration links, and tests

- Add MCP_DOMAIN and API_DOMAIN env vars with URL macros
- Add MCP Server guide to documentation pages
- Add integration links (REST API, MCP) on Access Tokens page
- Add API & AI Integration feature card on home page
- Add MCP tool and schema resource tests (46 tests)
- Update compiled frontend assets and composer.lock

* fix: add Gate authorization checks to all MCP update and delete tools

* fix: add rate limiting to API and MCP routes

* test: add API filter, sort, and disallowed-filter/sort tests

* test: add API pagination boundary tests

* test: add cross-team FK injection tests for people and opportunities

* test: add soft-delete visibility tests for API and MCP

* test: add input validation edge case tests

* test: add /api/user endpoint tests

* test: enhance MCP schema resource tests with structure validation

* test: add MCP validation and edge case tests

* feat: [US-001] - Enforce Sanctum token abilities on API and MCP routes

* feat: [US-002] - Fix production dependency and migration issues

* feat: [US-003] - Harden mass assignment and data exposure

* feat: [US-004] - Decouple List actions from global request and fix Octane safety

* feat: [US-005] - Add pagination and caching to unbounded endpoints

* feat: [US-006] - Fill critical test coverage gaps

* feat: [US-007] - Fill entity-level test parity gaps

* feat: [US-008] - Add missing MCP tool features and tests

* feat: [US-009] - Reduce MCP tool duplication with base classes

* feat: [US-010] - Clean up low-severity issues

* chore: update composer.lock to sync with composer.json

* fix: resolve all CI failures - pint style, test fixes, and scribe config guard

- Run pint on all files to fix 84+ style issues (fully_qualified_strict_types, ordered_imports, etc.)
- Fix ResolvesEntitySchema null-safe handling for validation_rules
- Fix ApiTeamScopingTest to match JSON:API response format from UserResource
- Fix CustomFieldsApiTest validation_rules format to match ValidationService::isRequired()
- Guard config/scribe.php with class_exists check for --no-dev Docker builds

* chore: remove tracked .context files from git cache

* fix: harden multi-tenant API security and reorganize tests

- replace switchTeam() DB write with in-memory forceFill/setRelation
- add creating/updating event guards on PersonalAccessToken for team ownership
- wrap token creation in DB::transaction to prevent orphaned tokens
- change token-team FK from nullOnDelete to cascadeOnDelete
- move /api/user into v1 group for consistent middleware chain
- remove duplicate throttle:api middleware from v1 routes
- remove dead creator_id/team_id assignments in Create actions (set by observers)
- use active() scope instead of where('active', true) in ResolvesEntitySchema
- split CriticalCoverageTest into ApiTeamScopingTest, ApiMiddlewareTest,
  TeamIdImmutabilityTest, and entity-specific test files
- add revoked team membership regression test

* fix: use query builder for Team::find in token creating event

* Feat/agent native positioning [UI/UX/COPY] (#148)

* feat: update positioning to agent-native CRM branding

Reposition Relaticle as "The Open-Source CRM Built for AI Agents"
across README, landing page hero, features grid, CTA section,
and meta tags. Reorder features to lead with Agent-Native
Infrastructure and Customizable Data Model.

* docs: add CRO-first launch design and product marketing context

Design doc for 10-day launch plan: page CRO polish, schema markup,
and coordinated multi-channel launch (Product Hunt, Twitter, Reddit,
HN, GitHub). Product marketing context captures positioning, audience,
competitive landscape, and brand voice for marketing skill automation.

* feat: CRO polish and AI discoverability for agent-native launch

- Rewrite features section header to "Built for humans. Accessible to AI."
- Apply "so what" test to all 10 feature descriptions with benefit-focused copy
- Add AI agent angles to Task Management and Notes feature cards
- Rewrite features bottom CTA with trust signals (900+ tests, AGPL-3.0)
- Update community section to "Built in the Open" with concrete proof points
- Add JSON-LD schema markup (SoftwareApplication, Organization, WebSite)
- Add explicit AI crawler allows to robots.txt (GPTBot, ClaudeBot, PerplexityBot)

* docs: add launch content and implementation plan

- Create launch content for Product Hunt, Show HN, Reddit, Twitter, and GitHub Release
- Add JSON-LD schema markup using spatie/schema-org (SoftwareApplication, Organization, WebSite)
- Add CRO-first launch implementation plan

* feat: add animated bento grid features section with SVG flow diagram

- Bento grid layout with 10 feature cards including agent-native hero
- SVG Bezier curve connectors between agents, MCP server, and CRM entities
- Motion.dev scroll-triggered animations (pathLength draw, stagger fade-in)
- Remix Icons for Claude, ChatGPT, Gemini agent logos
- Responsive mobile/desktop layouts with separate diagram views

* refactor: replace inline SVGs with Blade Remix Icons across homepage

- Features: 10 card icons + utility icons (add, check, arrow, chevron)
- Hero: arrow-right, github, shield-check
- Community: github, discord, file-text, arrow-right

* refactor: unify all marketing pages on Remix Icon with 3-mode theme switcher

Replace Heroicons and custom icon components with Remix Icon across header,
footer, mobile menu, and all homepage sections for visual consistency.
Add system/light/dark theme switcher in footer and mobile menu.

* Feat/agent native positioning (#179)

* update package.json

* feat: redesign hero section with premium UI

* feat: polish homepage sections UI

* fix: restore CTA section dots pattern, keep hero-style buttons

* feat: add content-aware animations to feature cards and redesign docs page

* feat: redesign homepage hero, header scroll behavior, and docs search

- Remove hero badge and purple gradient blobs, add diagonal SVG line
  frames on left/right sides for a cleaner geometric look
- Header nav pill: transparent by default, border/bg appears on scroll
  with smooth animated gap transitions
- Add purple glow effect to Company Management feature card
- Remove Open Source badge from community section
- Redesign docs search input: rounded pill, minimal focus styles,
  keyboard shortcut hint
- Refine "Built for AI Agents" accent underline

* feat: refresh feature card icons with refined choices and thinner sizes

- Replace icons: terminal-box→git-merge, sparkling→lightbulb-flash,
  database→stack, building→building-2, contacts→user-star,
  hand-coin→funds, team stays, task stays layout-masonry,
  file-transfer→download-cloud, sticky-note→quill-pen
- Reduce icon sizes for a more delicate, minimalist look

* feat: add hero tabs, animated tab switching, and decorative framing lines

* feat: simplify hero mockup glow to subtle primary accent

* hero variant v4: Source gradient + CRM with corner dots

* improve v4: enhanced gradient underline on Source + bigger glowing dots with connecting lines on CRM

* fix v4: all text black, remove dots, add lines left/right, underline full Open-Source

* remove decorative lines from Open-Source

* line variant 3: wavy SVG line

* remove Built for AI Agents underline + change Open-Source line to color-mix

* add rounded line caps to wavy underline

* fix: resolve Copilot AI code review issues

- Fix Tailwind class typo: lefta-0 → left-0 in hero section
- Remove non-functional mobile menu button from header

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.5 <[email protected]>

* feat: merge rest-api-mcp-server, add real hero screenshots, fix missing deps

- Merge feat/rest-api-mcp-server branch (API, MCP tools, Scribe docs)
- Replace broken .jpg hero references with real .png screenshots
- Add unique screenshots per hero tab (companies, pipeline, custom fields)
- Restore missing composer deps (blade-remix-icon, schema-org)
- Generate Scribe API documentation views
- Restore layout components (header, footer, mobile-menu)

* chore: integrate spatie/laravel-sluggable with reserved slug validation (#174)

* chore: install spatie/laravel-sluggable

* refactor: replace TeamObserver with spatie/laravel-sluggable HasSlug trait

* test: update slug uniqueness expectations for laravel-sluggable suffix format

* feat: add ValidTeamSlug rule with format and reserved slug validation

* fix: make ValidTeamSlug readonly to satisfy arch test

* fix: allow existing reserved slugs on update via ignoreValue parameter

* fix: close review gaps in slug validation, factory, and test coverage

- Replace raw regex with ValidTeamSlug in SystemAdmin TeamResource
- Override otherRecordExistsWithSlug to block reserved slugs at model level
- Remove redundant manual slug generation from TeamFactory
- Rename observer-referencing test descriptions
- Add integration tests for reserved slug rejection in create/update flows
- Add route coverage test to catch missing RESERVED_SLUGS entries
- Add missing reserved slugs: sysadmin, two-factor-challenge, team-invitations, horizon, user

* fix: resolve CI failures in rector, factory slug generation, and arch test

- Use self::query()->where() to satisfy EloquentMagicMethodToQueryBuilderRector
- Add preventOverwrite() so HasSlug skips generation when slug is preset
- Restore factory afterMaking slug callback for Event::fake() compatibility
- Ignore App\Rules in arch test for readonly rule classes

* chore: add polyscope workspace files to gitignore

* fix: resolve pint v1.29.0 style issues in ValidTeamSlug

* fix: update fathom analytics URL normalization for slug-based tenant URLs

* feat: wave 1 agent-native positioning and launch prep

- Install spatie/laravel-markdown-response for AI bot discoverability
- Add FAQPage JSON-LD schema and FAQ section with Alpine.js accordion
- Add BreadcrumbList JSON-LD to documentation pages
- Update stale test count from 900+ to 1,100+ across landing page
- Sharpen hero subheading and landing page copy
- Rewrite README for agent-native CRM positioning
- Polish all 5 launch content drafts (PH, Twitter, Reddit, HN, GitHub)
- Add implementation plan and design spec

* fix: restore missing packages after merge and update docs layout meta

- Re-add spatie/schema-org, spatie/laravel-markdown-response, andreiio/blade-remix-icon lost during merge
- Update documentation layout meta description for agent-native positioning

* fix: add 'mcp' to reserved team slugs to prevent route conflicts

* chore: gitignore .scribe directory and remove from tracking

* fix: restore spatie/laravel-query-builder package lost during merge

* fix: restore scribe, mcp, and scalar packages lost during merge

* fix: add missing route segments to reserved team slugs

Add newly discovered route segments (password-reset, email-verification,
users, scalar, engagement, system-administrators) and exclude _boost
prefix from the route coverage test.

* feat: polish homepage hero, header dark mode, and section transitions

- Add sparkle badge linking to MCP docs above hero headline
- Update subheadline to benefit-focused copy
- Fix header dark mode: solid background on initial load via Tailwind classes
- Restore Alpine.js theme-switcher component in footer
- Add gradient fade between FAQ and CTA sections

* chore: update dependencies to latest versions

- Bump Tailwind CSS to v4.2.1, PostCSS to v8.5.8, and Autoprefixer to v10.4.27
- Update Shiki packages to v4.0.2 for improved functionality
- Upgrade @esbuild and related platform builds to v0.27.4

* feat: polish hero section, header, and layout components

- Redesign AI agent tab with action sequence mockup
- Simplify header to consistent 64px height across all breakpoints
- Clean up mobile menu and footer markup
- Remove trailing arrow from hero CTA button
- Update preview images with higher resolution versions

* feat: polish community and FAQ sections

- Remove duplicate arrow from community cards, keep top-right only
- Add rel="noopener noreferrer" on external links
- Align community card styling with features cards (p-6, text-lg font-medium)
- Add scroll entrance animations to both sections
- Refactor FAQ to @foreach loop with data array
- Replace inline SVG chevron with ri-arrow-down-s-line component
- Add FAQ subtitle for consistency with other sections
- Fix FAQ h2 size to include md:text-[2.75rem]
- Fix FAQ divider opacity for dark mode consistency
- Improve FAQ accordion UX: hover state, cursor, smooth x-collapse
- Fix mobile stats bar horizontal divider between rows

* feat: improve features section spacing and responsiveness

- Unify all card padding to p-6
- Align CTA card title/desc with shared card variables
- Fix Agent-Native diagram: show desktop SVG at lg only, mobile flow below
- Fix Task Management: md:flex-row for tablet, stacked on mobile
- Fix orphaned cards at tablet: Sales and Notes span md:col-span-2
- Remove CTA trailing arrow for consistency
- Add gap-3 on mobile for better card spacing
- Replace start-building GitHub button with Get in touch
- Remove trust signal badges from start-building
- Unify checkbox icons to ri-checkbox-circle-fill with text-emerald-500

* feat: add contact page with unified marketing form components

- Create x-marketing.input and x-marketing.textarea components
- Fix focus ring: outline-none with proper focus:ring-2 focus:border-primary
- Refactor contact form to use new marketing components
- Add ContactController, ContactRequest, and contact mail

* fix: pricing page, documentation layout, and misc improvements

- Change Cloud plan price display from "Free to start" to "$0/mo"
- Remove misleading "Early Access" badge
- Unify check mark colors to emerald-500 across both plans
- Replace "SSL and domain included" with "No server maintenance"
- Fix documentation layout top padding to match pricing (pt-32 md:pt-40)
- Add .superpowers to gitignore

* feat: add marketing button component

- Unified button component with primary/secondary variants
- Support for leading/trailing icons and external link handling

* feat: add project guidelines, FCP optimizations, and icon consistency

- Track CLAUDE.md and .ai/guidelines/relaticle/ in git
- Add icon convention rules (fill for brands, line for UI)
- Add fetchpriority="high" and preload for hero LCP image
- Add theme-color meta tags for light/dark
- Fix Discord icon to fill variant across all pages
- Update laravel/boost to v2.3.1

* fix: board column actions losing stage/status on create

Column create actions (the "+" button per column) were not passing the
column ID through the Livewire roundtrip, causing new records to be
created without a stage/status assignment.

- Update OpportunitiesBoard and TasksBoard to use
  `$action->getArguments()['column']` instead of `$arguments['column']`
- Add amount and close date badges to opportunity board cards
- Hide stage field from board create form (auto-assigned from column)
- Add `formatCloseDateBadge()` for contextual date display
- Bump flowforge to v4.0.6 which fixes the root cause in core

* docs: add MCP custom field filtering design spec

- Custom field filtering with typed operators (eq, gt, gte, lt, lte, contains, in, has_any)
- Relationship includes via existing Spatie allowedIncludes
- CRM summary aggregation resource with pipeline/task stats
- Dynamic schema generation from team's active custom fields
- PostgreSQL-optimized query strategy using whereHas + value column indexes

* chore: retake hero screenshots at 2x retina with clean data

- All 6 screenshots now 2880x1800 (2x retina at 1440x900 display)
- Removed test data from pipeline (arev, barev, etc.)
- Companies: no empty custom field columns
- Pipeline: cards show company, amount badges, close date badges
- Custom Fields: no hover state artifacts
- WebP versions regenerated (50-60% smaller than PNG)

* docs: add MCP custom field filtering implementation plan

10-task plan covering: performance indexes, CustomFieldFilter,
CustomFieldSort, CustomFieldFilterSchema, BaseListTool refactor,
List action integration, CRM summary resource, and schema updates

* docs: add Task 0 to plan for missing CustomFieldType enum cases

FILE_UPLOAD and RECORD cases needed for type-safe exclusion in filter schema

* chore: add FILE_UPLOAD and RECORD cases to CustomFieldType enum

* feat: add performance indexes for custom field value filtering

* feat: add CustomFieldFilter for filtering by custom field values

Spatie QueryBuilder invokable filter that translates operator objects
(eq, gt, gte, lt, lte, contains, in, has_any) into whereHas queries
on the custom_field_values table. Supports max 10 conditions per
request and silently ignores unknown field codes.

* feat: polish documentation pages and hero section

- Docs index: Getting Started full-width highlighted card, 2x2 grid below
- Docs layout: remove purple gradient blobs, match homepage grid pattern
- Docs search: redesign to use divider-style results, subtle mark highlight
- Docs search: add Cmd+K keyboard shortcut handler
- Docs detail: permalink icons hidden by default, show on heading hover
- Docs detail: headings clickable with w-fit constraint
- Docs detail: hide H1 permalink entirely
- Hero: remove stats bar, remove entrance animations, add fetchpriority

* feat: add CustomFieldSort for sorting by custom field values

* feat: add CustomFieldFilterSchema for dynamic filter schema generation

* feat: add custom field filtering and includes to BaseListTool and ListOpportunities

Refactor BaseListTool.handle() to construct full HTTP Request with
filter/sort/include params. ListOpportunities now supports custom
field filtering via AllowedFilter::custom and dynamic sort registration.

* feat: add custom field filtering and sorting to all List actions

* feat: add CRM summary aggregation resource

Pipeline breakdown by stage with amounts, task overdue/due-this-week
counts, and record totals for all entities. Cached 60 seconds.

* feat: document filterable fields in MCP schema resources

* feat: redesign pricing page with improved visual hierarchy

- Add "Simple Pricing" badge above headline
- Add plan icons and "Recommended" badge on Cloud card
- Split features into shared and plan-specific groups with dividers
- Redesign trust signals as individual cards with icons
- Improve Help CTA layout with subtle glow
- Larger price text and better spacing throughout

* fix: unify check icon colors on pricing page

* feat: replace hero tab animations with directional crossfade

- Simultaneous crossfade with direction-aware slide (0.35s total)
- Use Motion.dev animate() instead of CSS @keyframes + animationend
- Remove 6 keyframe blocks and is-entering/is-leaving CSS rules
- Remove data-animation attributes from tab panels
- Pricing trust signal icons use primary color

* feat: upgrade spatie/laravel-query-builder to v7

- Convert all allowed* methods from array to variadic syntax (v7 API)
- Add AllowedInclude::count() aggregate includes for all CRM entities
  (Company, People, Opportunity, Task, Note)
- Update API resources with conditional count attributes via whenHas()
- Fix Scribe strategy regex to match variadic syntax
- Document aggregate includes in MCP schema resources
- Apply Rector improvements to custom filter/sort classes

* chore: apply rector fixes and add readonly to ContactController

* fix: implement ShouldQueue on NewContactSubmissionMail for arch test compliance

* feat: enable header toolbar on opportunities and tasks boards

* chore: update flowforge to v4.0.8 (header toolbar fixes)

* feat: add BaseShowTool and Get tools for all CRM entities

Adds a BaseShowTool abstract class and five concrete Get tools
(GetCompanyTool, GetPeopleTool, GetOpportunityTool, GetTaskTool,
GetNoteTool) so MCP clients can fetch a single record by ID with
optional relationship includes. Registers all Get tools in
RelaticleServer. Covers team-scoping and happy-path with new tests.

* feat: add WhoAmI MCP tool to expose authenticated user, team, and token context

Adds WhoAmiTool as the first registered tool in RelaticleServer so MCP
clients can discover who they are, their current team, team members, and
token abilities without querying CRM entities.

* feat: add inline relationship support to note and task MCP create/update tools

Notes and tasks can now be linked to companies, people, and opportunities
via *_ids arrays in create/update payloads. Tasks also accept assignee_ids.
CreateNote/CreateTask pull relationship IDs before attribute extraction and
sync inside the transaction. UpdateNote/UpdateTask use array_key_exists so
omitting a key leaves relationships untouched while an empty array detaches
all. Validation uses Rule::exists scoped to team_id; assignee validation
uses Rule::in against team member IDs.

* feat: add MCP attach/detach tools for notes and tasks

Adds BaseAttachTool and BaseDetachTool abstract base classes, then
concrete implementations for notes (companies/people/opportunities)
and tasks (companies/people/opportunities/assignees). Uses
syncWithoutDetaching() for attach and detach() for detach. Registers
all 4 tools in RelaticleServer with team-scoped validation.

* feat: add contact_id and relationship filters to MCP list tools

- Add contact_id filter to ListOpportunitiesTool and ListOpportunities action
- Add company_id, people_id, opportunity_id filters to ListTasksTool and ListTasks action
- Add writable_relationships and tools_hint to NoteSchemaResource and TaskSchemaResource
- Fix untyped closure parameters in BaseAttachTool and BaseDetachTool (type coverage)
- Apply Rector arrow function return type improvements to WhoAmiTool
- Add tests for contact_id opportunity filter and company_id task filter

* fix: add new MCP base tool classes to arch test ignore lists

---------

Co-authored-by: Ilya <[email protected]>
Co-authored-by: Claude Sonnet 4.5 <[email protected]>

* fix: apply uncommitted fixes from agent-native-positioning branch

- Remove stale address/country/phone from Company $fillable (now custom fields)
- Add Mcp::local() registration for stdio transport
- Add .gstack/ to .gitignore

* fix: stabilize user retention chart test across week boundaries

travelTo mid-week so the test data always falls within the same
weekly interval regardless of which day CI runs.

* fix: regenerate package-lock.json to sync with package.json after merge

* fix: exclude scribe doc routes from smoke test and fix terminate() signature

Scribe-generated files (OpenAPI spec, Postman collection) are gitignored
and not built in CI, causing docs/api* routes to return 500 in the smoke
test. Exclude them from the route smoke test.

Also fix SetApiTeamContext::terminate() to accept the standard Laravel
middleware terminate signature ($request, $response).

* fix: revert terminate() params per rector unused-parameter rule

* chore: remove product marketing context file and update test guidelines

- Delete `.claude/product-marketing-context.md` as it's no longer relevant.
- Add `$this->travelTo()` usage note in test guidelines to stabilize date-dependent tests.

* chore: update composer dependencies to latest versions

- Upgrade `blade-heroicons` to 2.7.0
- Upgrade `eloquent-power-joins` to 4.3.0
- Upgrade `laravel/framework` to v12.55.0
- Upgrade `laravel/mcp` to v0.6.3
- Upgrade `

* chore: retake pipeline preview screenshots with populated data

Seeded realistic opportunity data across all pipeline stages so the
hero showcase looks like an active CRM instead of a sparse demo.

* chore: update composer dependencies and remove unused packages

- Removed `anourvalar/eloquent-serialize` and `ryangjchandler/blade-capture-directive`.
- Upgraded `filament/*` packages to v5.4.0.
- Upgraded `laravel/framework` to v12.55.1.
- Upgraded additional dependencies like `laravel/horizon`, `propaganistas/laravel-phone`, and several `spatie/*` libraries.

* fix: add cursor-pointer to button and remove unused trailing icon attribute in contact form

* refactor: replace hero tab vanilla JS with Alpine.js + Motion One

Replace ~170 lines of imperative DOM code with two Alpine.js components
that use Motion One for animations. Tab state, indicator positioning,
dark mode image swapping, and chat animation are now declarative.

Key improvements:
- Immediate visual feedback on click (no animation lock)
- Rapid click support via WAAPI getAnimations().cancel()
- Cross-browser animation cleanup (Safari, Firefox, Edge, Chrome)
- No more window.mcpChatReset/mcpChatAnimate globals
- Components communicate via Alpine $dispatch events

* fix: improve hero tab animation smoothness on Safari

Replace position:absolute crossfade with CSS grid stacking to eliminate
layout thrashing during panel transitions. Add will-change hints before
animation to force GPU compositing in Safari.

Changes:
- Panel container uses grid with col-start-1/row-start-1 for overlap
- No more position:absolute/top/left/right manipulation during animation
- will-change: transform, opacity set before animation, cleared after
- Reduces reflows from 3 to 1 per tab switch

* feat: improve contact form with honeypot spam protection and dedicated config

- Add config/relaticle.php with contact.email for decoupled recipient config
- Add spatie/laravel-honeypot for invisible spam protection
- Add email:rfc,dns validation for real domain verification
- Move contact routes under ProvideMarkdownResponse middleware group
- Align success state design with brand primary color system

* fix: update footer description for clarity and transparency

- Revised CRM description to emphasize self-hosting and ownership benefits.

* fix: use visibility:hidden instead of display:none for Safari WAAPI compat

Safari's compositor doesn't have elements in its layer tree when they
transition from display:none to visible, causing WAAPI animations to
skip or stutter. Using visibility:hidden keeps panels in the compositor
layer tree so animations start immediately.

- Replace hidden class with invisible on tab panels
- Add showPanel/hidePanel helpers for clean visibility + z-index toggling
- z-index:1 on active panel ensures correct stacking during crossfade
- Grid stacking + visibility approach = zero layout reflows during animation

* fix: force WAAPI mode for all hero animations (Safari smoothness)

Motion's animate() has two engines: JS (main thread) when using
shorthand props like x/y, and WAAPI (compositor thread) when using
CSS property names like transform. We were using x/y which routed
through the JS engine, causing jank on Safari.

Switch all animations from shorthand to explicit CSS transform strings:
- { x: [0, 40] } → { transform: ['translateX(0px)', 'translateX(40px)'] }
- { y: [8, 0] } → { transform: ['translateY(8px)', 'translateY(0px)'] }

This forces WAAPI mode which runs on the GPU compositor thread in all
browsers including Safari.

* feat: redesign pricing cards with gradient top border and subtle shadow

- Replace border-2 with border + gradient accent bar at top of each card
- Add soft purple shadow to Cloud card for visual hierarchy
- Add subtle gray gradient bar to Self-Hosted card for consistency

* chore: upgrade composer dependencies (filament/* v5.4.1, phpseclib v3.0.50, sentry-laravel v4.23.0)

* fix: use uncontained aside section for API tokens table layout

* feat: reject unknown custom field keys in MCP and REST API

Replace CustomFieldValidationService with a unified ValidCustomFields
rule that handles both key validation (rejecting unknown field codes
with actionable error messages) and per-field value validation. This
ensures AI agents and API consumers get clear feedback when using
incorrect field codes instead of silent data loss.

* feat: add NotifyTaskAssignees action with diff-based notification

* refactor: add UpdateTask action wired to NotifyTaskAssignees

Extract task update logic into a dedicated action class that uses
the new diff-based NotifyTaskAssignees instead of notifying all
assignees. Notifications fire outside the DB transaction to prevent
sending if the transaction rolls back.

* feat: notify assignees on task creation via NotifyTaskAssignees

* feat: route Filament task creation through CreateTask action

* refactor: remove unit tests for NotifyTaskAssignees (covered by integration tests)

* refactor: use Filament native create with after hook for task notifications

Filament's CreateAction calls saveRelationships() after ->using(),
causing key mismatch with CreateTask action (assignees vs assignee_ids).
Instead, let Filament handle creation natively and fire notifications
via ->after() hook.

* refactor: expand skill descriptions for TailwindCSS, Livewire, and Pest testing

Update skill activation criteria to provide clearer guidance on when to trigger each skill. Remove redundant "When to Apply" sections. Improve examples and feature coverage for better usability.

* feat: add arch tests enforcing action pattern in API controllers and MCP tools

* docs: add comprehensive self-hosting guide

Add a dedicated self-hosting guide covering Docker deployment, environment
configuration, reverse proxy setup (Nginx/Caddy/Traefik), platform-specific
instructions for Dokploy and Coolify, backup/restore, manual deployment,
and troubleshooting. Simplify the developer guide's deployment section to
reference the new guide, and update the README accordingly.

* fix: escape LIKE wildcards in custom field contains filter

* fix: expose foreign key attributes in OpportunityResource and PeopleResource

* feat: add relationship validation to task API store/update requests

* feat: add relationship validation to note API store/update requests

* fix: add read ability check to WhoAmiTool

* fix: respect assigned_to_me filter value instead of treating any presence as true

* perf: cache custom field sort field resolution with 60s TTL

* refactor: unify documentation index page to consistent card grid layout

* fix: resolve PHPStan errors in task request team type hints

* docs: audit and fix public documentation quality

- rewrite Terms of Service and Privacy Policy with product-specific content
  (removes generic template referencing word limits and Basic plan)
- update MCP tools count from 20 to 30 across all surfaces (verified actual
  count from RelaticleServer tool registry)
- fix README self-hosting link (/documentation/self-hosting -> /docs/self-hosting)
- replace unsupported "Priority email support" pricing claim with "Email support"
- extract FAQ data to single source, eliminating duplication between visual
  accordion and schema.org structured data
- add "Edit on GitHub" link to documentation page template
- add per-page smoke tests for all 5 documentation guides, pricing page,
  and legal page content assertions
- create TODOS.md with deferred items (centralize marketing numbers,
  document 5-layer authorization model)

* feat: add navigation bar to API docs page

Customize the published Scribe Scalar template to include a sticky
nav bar with the Relaticle logomark, "API Reference" title, and
links back to Docs, MCP, Pricing, and GitHub. Prevents users from
being stranded on the standalone Scalar UI with no way back to the
main site.

* fix: use relative URLs in API docs navigation bar

Blade helpers (url(), route(), asset()) get baked into hardcoded
absolute URLs during scribe:generate, making them environment-specific.
Switch to relative paths (/docs, /pricing, /brand/logomark.svg) so
the generated output works in any environment without modification.

* fix: make ValidCustomFields readonly class to satisfy rector and arch test

* feat: make scribe:generate database-free for CI/CD

- Add Scribe::instantiateFormRequestUsing() with in-memory user/team
  to fix "currentTeam on null" during body parameter extraction
- Add Scribe::bootstrap() to set config flag for scribe-safe factories
- Add scribe-safe factory states that replace FK Factory references
  with plain ULIDs, preventing nested create() calls during make()
- Remove ResponseCalls strategy and DB transaction config
- Add #[ResponseFromApiResource] to all index() controller methods
- Create local CustomFieldFactory with realistic example data
- Wire App\Models\CustomField::newFactory() to local factory

* chore: remove local MCP route registration

* feat: update API docs navigation bar blade view

* fix: resolve select custom field values to {id, label} objects

Select and multi-select custom field values were returned as raw option
ULIDs instead of human-readable labels in both REST API and MCP responses.

- Update FormatsCustomFields trait to resolve choice field values to
  {id, label} objects using the options relationship
- Update all eager loading (12 files) to include .options for choice
  field resolution
- Non-choice fields (text, number, date, etc.) remain unchanged

* fix: resolve PHPStan nullsafe operator warnings in FormatsCustomFields

* fix: resolve remaining PHPStan nullsafe warning in multi-choice resolver

* fix: resolve MCP server audit issues (11 bugs, security, DX improvements)

- Fix partial custom_fields update wiping omitted fields (BUG-02) via
  CustomFieldMerger that loads existing values before save
- Fix MCP relationship includes not appearing in responses (BUG-01) by
  manually merging loaded relations into JSON output
- Fix GET endpoints crashing with *Count aggregate includes (BUG-05) by
  separating count vs relation includes in BaseShowTool
- Add XSS sanitization for rich-editor fields (SEC-02) using Symfony
  HtmlSanitizer across all create/update actions
- Fix CRM summary using raw ULID stage IDs (BUG-03) by resolving option labels
- Fix empty custom_fields serializing as [] instead of {} (BUG-06)
- Fix schema resources listing invalid include names (BUG-07)
- Add pagination metadata to MCP LIST responses (DX-03)
- Add field format hints and select options to schema resources (DX-01, DX-02)
- Add relationship counts to attach/detach responses (BUG-04)
- Expose accountOwner relationship on Company includes

* fix: guard resolveMultiChoiceValue against non-string array elements

Filter out non-scalar values before string casting in
resolveMultiChoiceValue to prevent Array to string conversion
errors when corrupted or nested array data exists in multi-choice
custom field values (link, email, phone fields).

* fix: resolve audit P1/P2 issues (validation, pagination, field leaks, response consistency)

- Validate select/multi-select option IDs exist on create/update,
  rejecting unknown IDs with Rule::in() against defined options
- Add deterministic pagination tiebreaker (orderBy id) to all 5 list
  actions preventing record overlap/skip with identical timestamps
- Allow clearing date/datetime custom fields via null by prepending
  nullable to date field validation rules
- Serialize relationship includes through API resource classes instead
  of raw toArray() to prevent leaking sensitive internal fields
  (email_verified_at, current_team_id, password, etc.)
- Nest relationship data inside data object in GET responses for
  consistency with LIST response structure

* fix: strip unloaded aggregate counts from nested includes, preserve {} in list responses

- Filter out *_count attributes from nested relationship serialization
  when the counts were never loaded on the model (prevents empty {}
  objects appearing in nested entity data)
- Switch BaseListTool to object-mode json_decode to preserve empty
  custom_fields as {} instead of [] through the encode round-trip

* fix: replace pure black (#000) with gray-950 in dark mode backgrounds

Swap dark:bg-black, dark:from-black, dark:via-black, dark:to-black with
gray-950 equivalents (#030712) across all marketing pages and layout
components. Reduces eye strain on digital screens, especially OLED
displays, while maintaining near-identical visual appearance.

* fix: return proper MCP tool errors instead of generic -32603

Replace findOrFail() and abort_unless() with find() and Response::error()
in all 5 base MCP tools so AI agents receive actionable error messages
("Company with ID [x] not found") instead of opaque internal errors.

* feat: move AI Agent tab to first position in hero section

Reorder hero tabs to lead with AI Agent (the product differentiator)
instead of Companies. Updates tab buttons, panel order, default active
tab, image loading priorities, and triggers chat animation on init.

* fix: catch invalid sort/filter/include queries in MCP list tools

Spatie QueryBuilder throws InvalidQuery (extends HttpException) for
unknown sorts, filters, and includes. This was bubbling up as generic
-32603. Now caught in BaseListTool and returned as Response::error()
with the descriptive message listing allowed values.

Also fixes Rector warning for unused foreach variable.

* fix: restore last_used_at datetime cast on PersonalAccessToken

* fix: reorder API middleware so ForceJsonResponse runs before auth:sanctum

* refactor: remove dead authorize() methods and duplicate custom_fields rules from form requests

* refactor: use #[CurrentUser] attribute, response()->noContent(), and $request->integer() in API controllers

* refactor: add Gate::authorize() to store/update/destroy controller methods for consistent authorization

* refactor: extract BelongsToTeamCreator trait from 5 identical observer creating() methods

* docs: document intentional pass-through for non-token requests in EnsureTokenHasAbility

* refactor: replace $listeners with #[On] attribute in ManageApiTokens

* refactor: extract inline callback filters to #[Scope] model methods on Note and Task

* fix: resolve PHPStan errors in BelongsToTeamCreator trait and model scopes

* docs: add design spec for API review fixes

Covers 5 targeted refactors from PR #136 code review:
- Remove redundant Gate::authorize() from controllers
- Move eager-loading into actions
- Create IndexRequest for per_page validation
- Fix FormatsCustomFields return type consistency

* fix: prevent TypeError crash when custom_fields is non-array input

The ValidatesCustomFields trait called toRules() with raw request input
during rule construction, before validation could reject non-array types.
Sending custom_fields as a string or integer caused a TypeError crash
(500) instead of a validation error (422).

Guard with is_array() check and change nullable to sometimes.

* docs: add rate limiting to API review fixes spec

Layered rate limiting: per-team ceiling (600/min), per-token
reads (300/min) and writes (60/min). Currently the api/mcp
rate limiters are defined but never applied.

* docs: add implementation plan for API review fixes

6 tasks covering authorization cleanup, eager-loading, IndexRequest,
FormatsCustomFields return type, and layered rate limiting.

* refactor: apply API review fixes -- authorization, eager-loading, validation, rate limiting

- Remove redundant Gate::authorize() from controllers (actions self-authorize)
- Move customFieldValues eager-loading from controllers into actions
- Create IndexRequest for per_page validation (replaces manual clamping)
- Fix FormatsCustomFields to always return stdClass
- Apply layered rate limiting: 600/min per team, 300/min reads, 60/min writes
- Move throttle middleware after auth:sanctum for token-based keying

* fix: add item-level validation rules for multi-value custom fields

Phone, email, and link fields define per-item validation rules
(phone:AUTO, email, url regex) via getItemValidationRules() but
toRules() never included them. Invalid items in arrays passed
validation silently.

* test: add item-level validation tests for link, email, and phone custom fields

Replace broken link tests that passed for the wrong reason (field didn't
exist, so "unknown field" rejection matched). New tests create the custom
field first, then verify item-level validation rules reject invalid
values (bad domain, bad email, bad phone) and accept valid ones.

* fix: resolve pint style issues in AppServiceProvider rate limiter

* refactor: simplify API request validation and rename ApiTokens to AccessTokens

- Eliminate ValidatesCustomFields traits (API + MCP) by consolidating
  custom field rule generation into ValidCustomFields::toRules()
- Rename ApiTokens -> AccessTokens across classes, views, and tests
  to match user-facing "Access Tokens" terminology
- Add @property-read Team|null $currentTeam to User model, removing
  stale @phpstan-ignore and redundant @var annotations
- Remove dead code: redundant casts() in PersonalAccessToken, unused
  navigation properties in AccessTokens page, authorize() in
  ContactRequest, per_page clamping in CustomFieldsController

* refactor: apply rector fixes for PHP 8.4 new-without-parentheses and closure return types

* update dependencies: bump multiple PHP package versions in composer.lock

* docs: document actions usage and laravel-best-practices skill addition

- Add section on using actions for write operations to ensure single-source-of-truth for business logic and side effects.
- Include notes on Filament CRUD usage and best practices for extracting inline logic.
- Add `laravel-best-practices` skill to guide adherence to Laravel coding patterns and refactoring standards.

* fix: apply laravel best practices — security, performance, and error handling

- Sanitize top-level string fields (name, title) in HtmlSanitizer,
  not just custom_fields, preventing stored XSS via API input
- Cache known custom field codes in ValidCustomFields to eliminate
  duplicate DB query on every create/update request
- Replace allUsers()->pluck('id') with direct DB query in Task
  requests to avoid hydrating full User models for ID collection
- Wrap NotifyTaskAssignees in defer() to send DB notifications
  post-response instead of blocking the API caller
- Replace whereHas with whereIn subqueries in User tenant scope
  for better index usage on team membership queries
- Add shouldRenderJsonWhen to bootstrap/app.php so exceptions
  outside middleware pipeline still return JSON for API routes
- Validate team ownership on PersonalAccessToken updating event
  (team_id is set post-creation via fill/save, bypassing creating)

* fix: address PR #136 review findings — security, validation, and test coverage

- Enforce read ability on MCP resources/prompts via native shouldRegister() hook
- Fix HtmlSanitizer XSS: recurse into nested array values in custom fields
- Add team-scoped Rule::exists() validation to MCP detach tools
- Redact available field list from custom field validation error messages
- Add null guard to TeamScope with whereRaw('1 = 0') to prevent data leaks
- Use $request->safe()->integer() for type-safe validated pagination params
- Make HtmlSanitizer and CustomFieldMerger readonly (fix arch test)
- Fix domains custom field unique constraint in CompaniesApiTest
- Exclude api/* from smoke tests (dedicated API test suite covers them)
- Remove redundant RefreshDatabase uses from 3 test files
- Add mutates() declarations, cursor pagination, PATCH, and unauth tests

* refactor: redesign hero tabs — pipeline first, MCP tool call indicators in AI agent preview

- Reorder hero tabs: Pipeline > AI Agent > Companies > Custom Fields
- Pipeline is now the default tab on page load
- Add visible MCP tool call indicators (create-people-tool, list-opportunities-tool) to AI agent preview
- Replace generic company names with distinctive ones (Kovra Systems, Meridian Health, Trellis Labs)
- Redesign deal cards as a single unified card with divide-y rows for consistency
- Add contact avatar with colored initials gradient
- Fix animation flash by hiding elements via CSS before JS animation fires
- Refine staggered animation sequence: avatar → tool call → text → card

* fix: remove user avatars from AI agent chat preview

Clean chat style matching Claude/ChatGPT — user messages are plain text,
only agent responses show the sparkle avatar.

* fix: restore user avatars and add conversation labels to AI agent preview

Add "You" and "Relaticle" labels with avatars to clearly show the
conversation between user and AI agent, matching Claude/ChatGPT patterns.

* fix: reframe AI agent preview as external chat using Relaticle MCP

Show ChatGPT as the AI assistant calling relaticle/ MCP tools, making it
clear Relaticle is the tool provider — not a built-in chatbot.

* fix: remove write-time HtmlSanitizer that corrupted plain-text fields

The Symfony HtmlSanitizer was designed for rich HTML content but was
applied to all string attributes including name/title fields. This
caused data corruption: apostrophes became &#039;, ampersands became
&amp;, and script tags produced empty names bypassing validation.

Following Filament's standard approach: store raw data, escape on
output. Blade {{ }} and Filament TextColumn handle XSS protection
at render time. The API returns JSON which clients escape themselves.

* fix: correct ILIKE escape order in CustomFieldFilter

Escape backslashes before % and _ to prevent double-escaping
when input contains literal wildcard characters.

---------

Co-authored-by: Ilya <[email protected]>
Co-authored-by: Claude Sonnet 4.5 <[email protected]>
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.

2 participants