Skip to content

feat(guard): a kit component cannot be forked in either app, and kit stories render in both Storybooks (#14776) - #18409

Closed
mrveiss wants to merge 2 commits into
mainfrom
issue-14776-kit-fork-guard
Closed

mrveiss wants to merge 2 commits into
mainfrom
issue-14776-kit-fork-guard

Conversation

@mrveiss

@mrveiss mrveiss commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Refs #14776
Refs #18241

Single-issue rationale

This is slice S0 of Refined UI step 3 (#18241). It adds the guard and the Storybook wiring that every later one-family-per-PR consolidation slice relies on. #14776 stays open: its forks (BaseButton, BaseBadge, BaseCard, EmptyState) are retired by later slices, and this PR only stops new ones.

Thinking Path

The owner rule is one canonical implementation per concept. @autobot/ui is the home for shared primitives, and BaseModal already proves the route (60 main-app importers, no local copy). Before consolidating each family, a guard has to stop a new fork appearing while the old ones are retired.

The existing hook pre-commit-no-cross-gui-duplicate already guards composable filenames and util/type names across both GUIs. This PR extends it to .vue components rather than adding a parallel guard.

What Changed

Guard: autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate gains a third check.

  • What fails: a .vue file in either app's src/ whose basename matches a kit component (libs/autobot-ui/src/components/*.vue) is rejected unless it is a thin wrapper importing that same name from @autobot/ui. The SLM SkipLink.vue is the precedent.
  • Kit name list: derived from the kit directory at run time, so it grows with the kit. If the derived list is empty, the check fails closed.
  • Grandfather list (KIT_FORK_GRANDFATHER): keyed per path, shrink-only and checked both ways. An entry whose file is gone, or has become a wrapper, fails as stale. It covers today's four forks, each verified to have no @autobot/ui import:
    • autobot-frontend/src/components/base/BaseButton.vue
    • autobot-frontend/src/components/base/BaseBadge.vue
    • autobot-frontend/src/components/base/BaseCard.vue
    • autobot-frontend/src/components/ui/EmptyState.vue
  • Tests: 12 new tests in the hook's test file.
    • Contrast pair: a fork fails, a wrapper passes.
    • Importing a different name from the kit is still a fork.
    • The list follows the kit directory.
    • Grandfathering is per path.
    • Both stale cases fail.
    • The empty-list case fails closed.
    • A real-tree control shows a non-empty list that covers every grandfathered name.
    • End to end: the hook blocks a fork; the real tree is clean.

Storybook: both apps' .storybook/main.js add '../../libs/autobot-ui/src/**/*.stories.ts', so kit stories render under each app's tokens.

BaseModal story and tests moved into the kit:

Behaviour delta: none at runtime.

Verification

Not done here: the _NOTIFICATION_ENTRY_POINTS over-count (it counts a workflow config composable as a toast system). It lives in repo_tests/frontend_fragmentation_ratchet_test.py:193, which open PR #18373 also edits, so it follows after #18373 merges.

Model Used

Claude Opus 5.5 (coordinating). Sonnet subagents did the planning and the implementation.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Storybook now includes stories from the shared UI kit in both front-end apps.
  • Bug Fixes

    • Added checks to prevent duplicate Vue components where a shared UI kit component should be used. The check reports outdated exceptions and blocks commits when violations are found.
    • Added coverage for modal focus management, including keyboard navigation and focus outside the dialog.
  • Documentation

    • Updated the developer guide and Storybook listing for the shared modal component.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

Both frontend Storybook configurations now include stories from the shared UI kit. BaseModal story and test coverage changes are in the kit. The pre-commit hook now checks application Vue files for forks of kit components.

Changes

Shared UI Kit Stories and BaseModal Coverage

Layer / File(s) Summary
Expose shared-kit stories in Storybook
autobot-frontend/.storybook/main.js, autobot-slm-frontend/.storybook/main.js, libs/autobot-ui/src/components/BaseModal.stories.ts, autobot-frontend/src/stories/Welcome.stories.ts
Both Storybook configurations include shared-kit stories. The BaseModal story imports the local component and uses the Kit/BaseModal title. The developer guide points to its updated path.
Update BaseModal test coverage
autobot-frontend/src/components/ui/__tests__/BaseModal.test.ts, libs/autobot-ui/src/components/__tests__/BaseModal.test.ts
The frontend BaseModal tests are removed. The shared-kit tests cover rendering, focus wrapping, default Tab behaviour, and focus remaining outside the dialog.

Shared UI Kit Fork Check

Layer / File(s) Summary
Define kit component fork detection
autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate, autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate_test.py
The hook discovers kit components and scans application Vue files for matching basenames. A matching file passes if it imports the same-named component from @autobot/ui or has an exact grandfathered path. Tests cover wrappers, forks, component discovery, and grandfathered paths.
Enforce and test the kit fork check
autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate, autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate_test.py
The hook includes kit violations in its blocking result and fails when kit discovery is empty. Tests cover vacuity, hook failure on a fork, and the real repository tree.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to ff6fd

The change adds a developer-hook check and Storybook configuration, with no runtime behavior change. The wrapper regex can let an aliased import of a different kit export pass as a wrapper, which weakens the guard in a narrow case. This is a small follow-up rather than a merge blocker.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 6 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the two main changes: preventing kit component forks in either app and making kit stories available in both Storybooks.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 6 files. (1 skipped: 1 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mrveiss

mrveiss commented Oct 12, 2026

Copy link
Copy Markdown
Owner Author

Review (Sonnet) at 4e1da09: approve. The kit-fork guard flags a new app-local .vue that matches a kit name. It exits FATAL when the kit set is empty (it can't report clean on nothing), and a grandfather entry fails if its file is gone, is now a wrapper, or names a removed kit component. The moved tests are still collected (libs/autobot-ui/vitest.config.ts include, plus the frontend-test.yml kit step), and the stories are moved, not copied. LOWs: an export { X } from '@autobot/ui' re-export counts as a fork (false positive), and matching is case-sensitive. Queued for the next train.

@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@github-actions

Copy link
Copy Markdown
Contributor

Notice: 29 open PRs — past the runaway threshold (25)

This is not a request to defer this PR. The actual limit is the open-PR cap (AUTOBOT_OPEN_PR_CAP, default 40), enforced at pre-push against NEW branches only -- pushing more commits to this PR's own branch is never blocked by it.

This notice only means the count is high enough to be worth a glance for a runaway — something opening PRs in a loop, or a merge pipeline that has stalled so nothing is draining.

Currently open:

If the queue is draining normally, ignore this. Otherwise:

  1. Check whether CI is dispatching at all — see the ci-dispatch-watchdog status on these PRs
  2. Merge the ones whose CI has finished and review has passed: gh pr merge <number> --squash --delete-branch
  3. Look for a loop opening near-identical PRs

Warn-only runaway detector — .github/workflows/pr-queue-gate.yml. It never blocks a merge.

This was referenced Oct 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 26c87110-1e11-43e6-a4dc-1de8dce4ba5e
📥 Commits

Reviewing files that changed from the base of the PR and between dbd8ac9 and ff6fd3b.

📒 Files selected for processing (8)
  • autobot-frontend/.storybook/main.js
  • autobot-frontend/src/components/ui/__tests__/BaseModal.test.ts
  • autobot-frontend/src/stories/Welcome.stories.ts
  • autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate
  • autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate_test.py
  • autobot-slm-frontend/.storybook/main.js
  • libs/autobot-ui/src/components/BaseModal.stories.ts
  • libs/autobot-ui/src/components/__tests__/BaseModal.test.ts
💤 Files with no reviewable changes (1)
  • autobot-frontend/src/components/ui/tests/BaseModal.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +219 to +223
pattern = (
r"import\s*\{[^}]*\b" + re.escape(name) + r"\b[^}]*\}\s*from\s*['\"]"
+ re.escape(KIT_PACKAGE) + r"['\"]"
)
return re.search(pattern, text) is not None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the wrapper regex so it does not match a name inside a different import statement.

The pattern import\s*\{[^}]*\bNAME\b[^}]*\}\s*from\s*['\"]@autobot/ui['\"] matches an import that lists NAME as an alias target. An example is import { Other as SkipLink } from '@autobot/ui'. The file then counts as a wrapper although it does not import the kit component. The pattern also matches import type { NAME } only if the type keyword is absent. The PR notes already flag that an export { X } from re-export is a false positive in the other direction.

Match the name as a complete import specifier. Reject an as alias that renames a different export to NAME.

Proposed fix
--- "a/autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate"
+++ "b/autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate"
@@ -216,10 +216,10 @@
         text = path.read_text(encoding="utf-8")
     except OSError:
         return False
     pattern = (
-        r"import\s*\{[^}]*\b" + re.escape(name) + r"\b[^}]*\}\s*from\s*['\"]"
+        r"import\s*\{[^}]*(?:[{,]\s*)" + re.escape(name) + r"\s*(?=[,}])[^}]*\}\s*from\s*['\"]"
         + re.escape(KIT_PACKAGE) + r"['\"]"
     )
     return re.search(pattern, text) is not None
 
 

Comment on lines +297 to +306
kit_violations = collect_kit_fork_violations(repo_root)

vacuity_exit = _check_vacuity(comp_scanned, vocab_scanned, len(kit_component_names(repo_root)))
if vacuity_exit is not None:
return vacuity_exit

violations = comp_violations + type_violations
violations = comp_violations + type_violations + kit_violations
if not violations:
print(f"No cross-GUI duplicates found ({comp_scanned} composables, {vocab_scanned} utils/types files scanned).")
print(f"No cross-GUI duplicates found ({comp_scanned} composables, {vocab_scanned} utils/types files scanned, "
f"{len(kit_component_names(repo_root))} kit components checked).")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Compute kit_component_names(repo_root) once.

The function runs three times in main (once inside collect_kit_fork_violations, then at Lines 299 and 306). Each call globs the kit directory again. Compute the set once and pass it down. This also keeps the vacuity count and the scanned names consistent.

@mrveiss

mrveiss commented Oct 12, 2026

Copy link
Copy Markdown
Owner Author

Landed via merge train #7b (#18496, merge e8ea48c) at its approved head 4e1da09, which is an ancestor of main. The branch's later commit ff6fd3b only merges main in and adds no content of its own, so this PR is closed as delivered rather than auto-marked merged. Refs #14776, #18241.

@mrveiss mrveiss closed this Oct 12, 2026
@mrveiss
mrveiss deleted the issue-14776-kit-fork-guard branch October 12, 2026 19:39
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