Skip to content

feat(plugin-blocker): add blockerPlugin and useBlocker hook#682

Open
ENvironmentSet wants to merge 38 commits intomainfrom
add-plugin-blocker
Open

feat(plugin-blocker): add blockerPlugin and useBlocker hook#682
ENvironmentSet wants to merge 38 commits intomainfrom
add-plugin-blocker

Conversation

@ENvironmentSet
Copy link
Collaborator

@ENvironmentSet ENvironmentSet commented Mar 10, 2026

문제

모바일 웹뷰 기반 서비스 팀들이 "화면 이탈 방지 UX"(뒤로가기 시 확인 다이얼로그 등)를 각자 Stackflow 바깥에서 구현하고 있다. history.block(), 네이티브 브릿지 등 외부 API에 의존하다 보니 connectBackButtonsPlugin 도입 시 충돌이 발생하고, 동일 문제를 여러 팀이 중복 해결하고 있다.

Stackflow 코어에는 onBefore* + preventDefault() 메커니즘이 있지만, 개별 액티비티 컴포넌트가 "나로부터의 이탈을 막아줘"라고 선언할 수 있는 인터페이스가 없었다.

해결

@stackflow/plugin-blocker 패키지를 추가한다.

useBlocker({ shouldBlock, onBlocked })으로 액티비티 컴포넌트가 네비게이션 차단 정책을 선언한다:

  • shouldBlock(action): 차단 여부를 판단하는 predicate. pop뿐 아니라 push, replace, step 계열 모두 지원.
  • onBlocked(nav, { proceed }): 차단 시 호출되는 콜백. 확인 다이얼로그 등 UX를 자유롭게 구현하고, proceed()로 네비게이션을 허용.

Blocking set 모델: 여러 블로커가 동시에 차단할 수 있고, 모든 블로커가 proceed()해야 네비게이션이 실행된다.

구현 세부사항

  • Symbol 마커 기반 replay: proceed() 후 액션을 재실행할 때, 글로벌 boolean 플래그 대신 action params에 인스턴스별 Symbol 마커를 주입하여 replay 여부를 식별. 다른 플러그인이 onBefore* 훅에서 별도 네비게이션을 실행해도 해당 네비게이션이 blocker 판단을 우회하지 않음.
  • 알림 순서 보장: onBlocked 내에서 새 네비게이션이 발생하면 큐에 적재하여 재진입 없이 발생 순서대로 알림 (depth-first → breadth-first)
  • 오류 격리: 하나의 onBlocked가 throw해도 다른 블로커의 알림은 정상 실행. blockerPlugin({ onError }) 옵션으로 커스텀 에러 처리 가능
  • Lifecycle 안전성: 컴포넌트 unmount 시 블로커 자동 해제, 이전에 캡처된 proceed는 여전히 호출 가능

테스트

37개 테스트 케이스:

  1. 기본 차단 (6개 action type × block/allow)
  2. 선택적 차단 (shouldBlock predicate)
  3. 활성 액티비티 스코프
  4. onBlocked 통보
  5. proceed (단일/다중/멱등)
  6. 다중 블로커 합성
  7. Lifecycle (unmount 후 블로커 해제 및 proceed)
  8. 오류 격리
  9. 알림 순서 (재진입 불가, 발생 순서 보장)
  10. 다른 플러그인과의 상호작용 (earlier/later 플러그인의 onBefore* 훅 호출, 중첩 네비게이션 blocker 통과, preventDefault 존중)

🤖 Generated with Claude Code

ENvironmentSet and others added 9 commits March 10, 2026 16:27
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…brary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrite blockerPlugin to match the tech spec: no-arg blockerPlugin(),
useBlocker hook with shouldBlock/onBlocked/bypass, and supporting
NavigationEvent/BlockedNavigation types. Implementations are stubs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce a comprehensive technical specification document for the blockerPlugin, detailing the background, problem definition, design direction, public API, and usage examples. This document aims to unify the implementation of screen exit prevention across various mobile webview-based services.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

🦋 Changeset detected

Latest commit: 8412b23

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@stackflow/plugin-blocker Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a navigation blocker plugin to prevent navigation based on custom conditions.
    • Introduced useBlocker hook to define activity-specific blocking policies and handle blocked navigation events.
    • Support for multiple blockers working together with configurable error handling for failed navigation blocks.

Walkthrough

Introduces a new blocker plugin for Stackflow that enables navigation control via a useBlocker hook and blockerPlugin export. Provides per-activity blocking policies, multiple-blocker composition, isolated error handling, and navigation replay logic when blockers proceed.

Changes

Cohort / File(s) Summary
Changelog & Package Registration
.changeset/add-plugin-blocker.md, .pnp.cjs
Documents new blocker plugin feature with API additions; registers plugin in runtime package registry with dependencies and peer dependency configurations.
Build & Module Configuration
extensions/plugin-blocker/esbuild.config.js, extensions/plugin-blocker/package.json, extensions/plugin-blocker/tsconfig.json
Sets up esbuild configuration for CommonJS/ESM dual builds, package manifest with exports/entry points, and TypeScript compiler options targeting ESNext with declaration emission.
Core Implementation
extensions/plugin-blocker/src/blockerPlugin.ts
Implements BlockerStore-based runtime managing blockers across activities, blockerPlugin factory integrating with Stackflow lifecycle hooks (onBeforePush/Pop/Replace/StepPush/StepPop/StepReplace), useBlocker hook for per-activity registration, and navigation replay logic with error isolation and dispatch queue management.
Public API Exports
extensions/plugin-blocker/src/index.ts
Re-exports NavigationAction and BlockedNavigation types, blockerPlugin factory, and useBlocker hook from blockerPlugin module.
Comprehensive Test Suite
extensions/plugin-blocker/src/blockerPlugin.spec.tsx
Validates blocking/allowing behavior across navigation actions, activity scoping with reactivation, onBlocked callbacks and proceed idempotency, multi-blocker composition, lifecycle cleanup, error isolation across blockers, notification ordering, and interactions with plugins applied before/after blockerPlugin.

Sequence Diagram(s)

sequenceDiagram
    participant Activity as Activity
    participant Blocker as useBlocker Hook
    participant Plugin as blockerPlugin
    participant Store as BlockerStore
    participant Action as Navigation Action

    Activity->>Blocker: Register blocker with shouldBlock & onBlocked
    Blocker->>Store: Register/unregister blocker on mount/unmount
    
    Action->>Plugin: Navigation triggered (push/pop/replace/step)
    Plugin->>Store: Query active blockers from active activities
    Store-->>Plugin: Return blockers matching navigation action
    
    alt Any blocker shouldBlock returns true
        Plugin->>Plugin: Prevent default navigation
        Plugin->>Blocker: Invoke onBlocked(blockedNavigation, {proceed})
        Blocker-->>Plugin: Store proceed callback reference
        
        loop Until all blockers call proceed
            Blocker->>Blocker: Handle blocking logic
        end
        
        Blocker->>Plugin: proceed() called
        Plugin->>Store: Check if all blockers have proceeded
        
        alt All blockers proceeded
            Plugin->>Plugin: Replay original navigation action
            Plugin->>Action: Navigation executes
        end
    else All blockers allow (shouldBlock returns false)
        Plugin->>Action: Navigation executes normally
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(plugin-blocker): add blockerPlugin and useBlocker hook' accurately describes the main change: introduction of two new public APIs for a new plugin package.
Description check ✅ Passed The description comprehensively explains the problem, solution, implementation details, and test coverage related to the blockerPlugin and useBlocker hook additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-plugin-blocker
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 10, 2026

  • @stackflow/demo

    yarn add https://pkg.pr.new/daangn/stackflow/@stackflow/plugin-blocker@682.tgz
    

commit: 8412b23

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 10, 2026

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8412b23
Status: ✅  Deploy successful!
Preview URL: https://325bdf91.stackflow-demo.pages.dev
Branch Preview URL: https://add-plugin-blocker.stackflow-demo.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 10, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
stackflow-docs 8412b23 Commit Preview URL Mar 15 2026, 05:00 PM

ENvironmentSet and others added 14 commits March 11, 2026 00:28
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ride(fn) API

bypass was tied to replaying a specific BlockedNavigation object.
override(fn) wraps arbitrary navigation in a callback, making the
blocker skip check scoped to the callback's synchronous execution.
This gives developers a unified, general-purpose way to bypass
a blocker for any navigation — not just a previously blocked one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace override pattern with proceed in onBlocked callback
- Update section 2, 3, 4, 5 test assertions for new API
- Update useBlocker signature: return void, proceed via onBlocked
- Fix NavigationEvent → NavigationAction export in index.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ENvironmentSet and others added 7 commits March 14, 2026 18:34
…gation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- wrap each onBlocked call in try/catch so one blocker throwing
  does not abort other blockers' notifications
- blockerPlugin() now accepts { onError } option for custom error
  handling; defaults to console.error
- add test: "6. 오류 격리"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add dispatching flag and notifyQueue to BlockerStore
- when handleBeforeNavigation is called re-entrantly (e.g. a navigation
  triggered inside onBlocked), queue the notification instead of
  dispatching immediately
- flush the queue after the current dispatch loop completes, ensuring
  onBlocked is always called in navigation occurrence order
- add tests: "7. 알림 순서" (no re-entrancy, occurrence-order guarantee)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ENvironmentSet ENvironmentSet changed the title [WIP] plugin-blocker feat(plugin-blocker): plugin-blocker Mar 14, 2026
@ENvironmentSet ENvironmentSet changed the title feat(plugin-blocker): plugin-blocker feat(plugin-blocker): add blockerPlugin and useBlocker hook Mar 14, 2026
ENvironmentSet and others added 5 commits March 15, 2026 01:07
If a plugin registered before blockerPlugin throws in its onBefore*
hook, our hook never runs and skipNext stays true, causing the next
unrelated navigation to be silently skipped. Wrap the action method
call in try/finally so skipNext is always reset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
skipNext was a mutable flag on the store, so when an earlier plugin
triggered a different navigation inside its onBefore* hook during
replay, blockerPlugin consumed the flag for the wrong action —
silently bypassing the blocker for the nested navigation while
re-blocking the intended replay.

Instead, stamp a unique Symbol onto the replayed action's params.
Only the exact action object that was replayed carries the marker,
so unrelated navigations fired by other plugins always go through
normal blocker evaluation. The Symbol is instance-scoped so
multiple blockerPlugin instances cannot interfere with each other.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ENvironmentSet ENvironmentSet marked this pull request as ready for review March 15, 2026 16:53
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ENvironmentSet ENvironmentSet self-assigned this Mar 15, 2026
@ENvironmentSet ENvironmentSet requested a review from orionmiz March 15, 2026 16:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
extensions/plugin-blocker/src/blockerPlugin.ts (1)

243-268: Consider memoizing callbacks or documenting the re-registration behavior.

The useEffect at Lines 254-261 re-registers the blocker whenever options.shouldBlock or options.onBlocked changes. This is intentional to pick up the latest callbacks, but if users pass inline functions without memoization, the blocker entry will be overwritten on every render. While functionally correct (same id key), this could be documented or the hook could accept stable references.

Additionally, store.blockers in the dependency array is unnecessary since it's a stable Map reference, though harmless.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/plugin-blocker/src/blockerPlugin.ts` around lines 243 - 268, The
useBlocker hook currently re-registers the blocker whenever options.shouldBlock
or options.onBlocked changes (and includes the stable Map store.blockers
unnecessarily); to fix, remove store.blockers from the dependency arrays and
either document that callers should pass stable callbacks to useBlocker or
change the registration to keep a stable entry and update the latest callbacks
via refs (e.g., keep idRef and blocker entry stable in useBlocker and store the
latest options.shouldBlock/options.onBlocked in mutable refs that the store
entry reads), referencing the useBlocker function, options.shouldBlock,
options.onBlocked, idRef, useEffect, and store.blockers to locate the code to
change.
extensions/plugin-blocker/esbuild.config.js (1)

29-29: Consider logging the error before exiting.

The .catch(() => process.exit(1)) discards the error details, making build failures harder to diagnose.

🔧 Proposed fix
-]).catch(() => process.exit(1));
+]).catch((err) => {
+  console.error(err);
+  process.exit(1);
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/plugin-blocker/esbuild.config.js` at line 29, The current promise
catch at the end of the esbuild build chain swallows the error (the `.catch(()
=> process.exit(1))` call); update that catch to accept the error parameter and
log the error (including message/stack) before calling process.exit(1) so build
failures are visible—i.e., modify the final `.catch` on the esbuild build
promise to log a clear message and the error details (error/stack) and then
exit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@extensions/plugin-blocker/package.json`:
- Line 30: The dev script in package.json uses "&&" so "build:dts --watch" never
starts because "build:js --watch" never exits; change the "dev" script to run
both watchers in parallel by using a concurrent runner (e.g., add "concurrently"
or "npm-run-all" as a devDependency) and update the "dev" script to invoke both
"build:js --watch" and "build:dts --watch" concurrently (optionally with flags
to kill others on exit for proper signal handling). Ensure the package.json
scripts reference the existing "build:js" and "build:dts" script names and add
the chosen tool to devDependencies so CI/local runs work consistently.

---

Nitpick comments:
In `@extensions/plugin-blocker/esbuild.config.js`:
- Line 29: The current promise catch at the end of the esbuild build chain
swallows the error (the `.catch(() => process.exit(1))` call); update that catch
to accept the error parameter and log the error (including message/stack) before
calling process.exit(1) so build failures are visible—i.e., modify the final
`.catch` on the esbuild build promise to log a clear message and the error
details (error/stack) and then exit.

In `@extensions/plugin-blocker/src/blockerPlugin.ts`:
- Around line 243-268: The useBlocker hook currently re-registers the blocker
whenever options.shouldBlock or options.onBlocked changes (and includes the
stable Map store.blockers unnecessarily); to fix, remove store.blockers from the
dependency arrays and either document that callers should pass stable callbacks
to useBlocker or change the registration to keep a stable entry and update the
latest callbacks via refs (e.g., keep idRef and blocker entry stable in
useBlocker and store the latest options.shouldBlock/options.onBlocked in mutable
refs that the store entry reads), referencing the useBlocker function,
options.shouldBlock, options.onBlocked, idRef, useEffect, and store.blockers to
locate the code to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bdd3efdd-1337-445f-a7ca-506781a78c09

📥 Commits

Reviewing files that changed from the base of the PR and between a43dbe4 and 8412b23.

⛔ Files ignored due to path filters (77)
  • .yarn/cache/@babel-code-frame-npm-7.29.0-6c4947d913-199e15ff89.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-helper-validator-identifier-npm-7.28.5-1953d49d2b-8e5d9b0133.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-darwin-arm64-npm-0.27.3-4c8fed986d-10.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-darwin-x64-npm-0.27.3-f27535b6d7-10.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-linux-arm64-npm-0.27.3-a9fedc262b-10.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-linux-ia32-npm-0.27.3-f45d477f3e-10.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-linux-x64-npm-0.27.3-2041dd7b27-10.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@testing-library-dom-npm-10.4.1-928d6cd2a7-7f93e09ea0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@testing-library-react-npm-16.3.2-67b0b894c8-0ca88c6f67.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@types-aria-query-npm-5.0.4-51d2b61619-c0084c389d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@types-jsdom-npm-20.0.1-5bb899e006-15fbb9a0bf.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@types-tough-cookie-npm-4.0.5-8c5e2162e1-01fd82efc8.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/abab-npm-2.0.6-2662fba7f0-ebe95d7278.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/acorn-globals-npm-7.0.1-97c48c0140-2a2998a547.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/acorn-npm-8.16.0-b2096bf83f-690c673bb4.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/acorn-walk-npm-8.3.5-871d141ed6-f52a158a1c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/aria-query-npm-5.3.0-76575ac83b-c3e1ed127c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/async-function-npm-1.0.0-a81667ebcd-1a09379937.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/async-generator-function-npm-1.0.0-14cf981d13-3d49e7acbe.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/asynckit-npm-0.4.0-c718858525-3ce727cbc7.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/balanced-match-npm-4.0.4-fd666b3c7f-fb07bb66a0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/brace-expansion-npm-5.0.4-acb9332524-cfd57e20d8.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/combined-stream-npm-1.0.8-dc14d4a63a-2e969e637d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/cssom-npm-0.3.8-a9291d36ff-49eacc8807.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/cssom-npm-0.5.0-44ab2704f2-b502a315b1.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/cssstyle-npm-2.3.0-b5d112c450-46f7f05a15.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/data-urls-npm-3.0.2-c8b2050319-033fc3dd0f.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/decimal.js-npm-10.6.0-a72c1b8a2f-c0d45842d4.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/delayed-stream-npm-1.0.0-c5a4c4cc02-46fe6e83e2.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/dequal-npm-2.0.3-53a630c60e-6ff05a7561.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/dom-accessibility-api-npm-0.5.16-d3e2310666-377b4a7f9e.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/domexception-npm-4.0.0-5093673f9b-4ed443227d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/entities-npm-6.0.1-84692dab43-62af130720.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/es-set-tostringtag-npm-2.1.0-4e55705d3f-86814bf8af.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/esbuild-npm-0.27.3-85b6c20323-aa74b8d8a3.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/escodegen-npm-2.1.0-e0bf940745-47719a65b2.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/estraverse-npm-5.3.0-03284f8f63-37cbe6e9a6.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/esutils-npm-2.0.3-f865beafd5-b23acd2479.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/form-data-npm-4.0.5-c35fce815a-52ecd6e927.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/generator-function-npm-2.0.1-aed34a724a-eb7e7eb896.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/get-intrinsic-npm-1.3.1-2f734f40ec-bb579dda84.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/glob-npm-13.0.6-864eb0cece-201ad69e5f.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/has-tostringtag-npm-1.0.2-74a4800369-c74c5f5cee.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/html-encoding-sniffer-npm-3.0.0-daac3dfe41-707a812ec2.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/is-potential-custom-element-name-npm-1.0.1-f352f606f8-ced7bbbb64.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/jest-environment-jsdom-npm-29.7.0-0b72dd0e0b-23bbfc9bca.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/jsdom-npm-20.0.3-906a2f7005-a4cdcff5b0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/mime-db-npm-1.52.0-b5371d6fd2-54bb60bf39.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/mime-types-npm-2.1.35-dd9ea9f3e2-89aa9651b6.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/minimatch-npm-10.2.4-11f0605299-aea4874e52.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/minipass-npm-7.1.3-b73a16498d-175e4d5e20.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/nwsapi-npm-2.2.23-aa3710d724-aa4a570039.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/parse5-npm-7.3.0-b0410074a3-b0e48be20b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/path-scurry-npm-2.0.2-f10aa6a77e-2b4257422b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/picocolors-npm-1.1.1-4fede47cf1-e1cf46bf84.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/pretty-format-npm-27.5.1-cd7d49696f-248990cbef.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/psl-npm-1.15.0-410584ca6b-5e7467eb51.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/punycode-npm-2.3.1-97543c420d-febdc4362b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/querystringify-npm-2.2.0-4e77c9f606-46ab16f252.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/react-is-npm-17.0.2-091bbb8db6-73b36281e5.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/requires-port-npm-1.0.0-fd036b488a-878880ee78.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/rimraf-npm-6.1.3-409ea7254f-dd98ec2ad7.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/saxes-npm-6.0.0-31558949f5-97b50daf6c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/symbol-tree-npm-3.2.4-fe70cdb75b-c09a00aadf.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/tough-cookie-npm-4.1.4-8293cc8bd5-75663f4e2c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-b09a15886c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/universalify-npm-0.2.0-9984e61c10-e86134cb12.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/url-parse-npm-1.5.10-64fa2bcd6d-c9e96bc8c5.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/w3c-xmlserializer-npm-4.0.0-f09d0ec3fc-9a00c412b5.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-4c4f65472c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/whatwg-encoding-npm-2.0.0-d7451f51b4-162d712d88.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/whatwg-mimetype-npm-3.0.0-5b617710c1-96f9f628c6.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/whatwg-url-npm-11.0.0-073529d93a-dfcd51c6f4.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/ws-npm-8.19.0-c967c046a5-26e4901e93.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/xml-name-validator-npm-4.0.0-0857c21729-f9582a3f28.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/xmlchars-npm-2.2.0-8b78f0f5e4-4ad5924974.zip is excluded by !**/.yarn/**, !**/*.zip
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • .changeset/add-plugin-blocker.md
  • .pnp.cjs
  • extensions/plugin-blocker/esbuild.config.js
  • extensions/plugin-blocker/package.json
  • extensions/plugin-blocker/src/blockerPlugin.spec.tsx
  • extensions/plugin-blocker/src/blockerPlugin.ts
  • extensions/plugin-blocker/src/index.ts
  • extensions/plugin-blocker/tsconfig.json

"build:dts": "tsc --emitDeclarationOnly",
"build:js": "node ./esbuild.config.js",
"clean": "rimraf dist",
"dev": "yarn build:js --watch && yarn build:dts --watch",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dev script won't run both watchers concurrently.

The && operator waits for the first command to exit before starting the second. Since --watch keeps build:js running indefinitely, build:dts --watch will never start.

🔧 Proposed fix using concurrent execution
-    "dev": "yarn build:js --watch && yarn build:dts --watch",
+    "dev": "yarn build:js --watch & yarn build:dts --watch",

Alternatively, consider using a tool like concurrently or npm-run-all for more robust parallel execution with proper signal handling.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"dev": "yarn build:js --watch && yarn build:dts --watch",
"dev": "yarn build:js --watch & yarn build:dts --watch",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/plugin-blocker/package.json` at line 30, The dev script in
package.json uses "&&" so "build:dts --watch" never starts because "build:js
--watch" never exits; change the "dev" script to run both watchers in parallel
by using a concurrent runner (e.g., add "concurrently" or "npm-run-all" as a
devDependency) and update the "dev" script to invoke both "build:js --watch" and
"build:dts --watch" concurrently (optionally with flags to kill others on exit
for proper signal handling). Ensure the package.json scripts reference the
existing "build:js" and "build:dts" script names and add the chosen tool to
devDependencies so CI/local runs work consistently.

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