* docs: move No Fee Campaign to the new Features Architecture * docs: add tasks; adjust requirements in `quicktasks.md` * refactor(no-fee-campaign): migrate to the new feature architecture pattern
15 KiB
Feature Specification: Migrate No Fee Campaign to Feature Architecture
Feature Branch: 004-migrate-no-fee-campaign
Created: 2025-01-27
Status: Draft
Input: User description: "I want to move the No Fee Campaign to the new Feature architecture (see @apps/web/docs/feature-architecture.md )."
Clarifications
Session 2025-01-27
- Q: How should the feature flag be mapped when creating the feature handle? The folder name is
no-fee-campaignbut the flag isFEATURES.NO_FEE_NOVEMBER. → A: Omit the second parameter - use semantic mapping. TheFEATURE_FLAG_MAPPINGincreateFeatureHandle.tsalready contains'no-fee-campaign': FEATURES.NO_FEE_NOVEMBER, socreateFeatureHandle('no-fee-campaign')will automatically use the correct flag. - Q: Which hooks should be exported directly from
index.ts? There are three hooks:useIsNoFeeCampaignEnabled,useNoFeeCampaignEligibility, anduseGasTooHigh. → A: Export all three hooks directly fromindex.ts. They are lightweight, used by external consumers, and should remain accessible.useIsNoFeeCampaignEnabledcan coexist with the feature handle'suseIsEnabled()for backward compatibility and convenience. - Q: Which components should be included in the feature contract? There are three components: NoFeeCampaignBanner, NoFeeCampaignTransactionCard, and GasTooHighBanner. → A: Include all three components in the contract. They are all used by external consumers and should be accessible via
useLoadFeature(). - Q: How should remaining edge cases be handled? → A: All edge cases clarified in the Edge Cases section. Eligibility data loading, feature flag toggling, chain switching, blocked addresses, and gas limit checks all maintain existing behavior - the feature architecture provides reactive handling via
useLoadFeaturehook dependencies, while business logic (eligibility, gas limits) remains unchanged per FR-010 and FR-011.
User Scenarios & Testing (mandatory)
User Story 1 - No Fee Campaign Feature Remains Functional After Migration (Priority: P1)
After migration, all existing No Fee Campaign functionality must continue to work exactly as before. Users should see campaign banners, eligibility information, and transaction execution options without any visible changes to behavior or user experience.
Why this priority: This is a refactoring task - the primary goal is maintaining existing functionality while improving code organization. Any regression would directly impact users' ability to use the sponsored transaction feature.
Independent Test: Can be fully tested by verifying that all No Fee Campaign UI components render correctly, eligibility checks work, and transaction execution with the campaign option functions as expected. This delivers the core value of maintaining feature parity.
Acceptance Scenarios:
- Given a user on a chain where No Fee Campaign is enabled, When they view the dashboard, Then the No Fee Campaign banner appears in the news carousel if they are eligible
- Given a user viewing the transaction page, When they are eligible for No Fee Campaign, Then the No Fee Campaign transaction card displays with eligibility information
- Given a user executing a transaction, When they are eligible and have remaining sponsored transactions, Then the No Fee Campaign execution method option is available in the execution selector
- Given a user with a transaction that exceeds gas limits, When they view the execution options, Then the Gas Too High banner appears and No Fee Campaign option is disabled appropriately
- Given a user who has reached their sponsored transaction limit, When they view execution options, Then the No Fee Campaign option shows limit reached state
- Given a user on a chain where No Fee Campaign is disabled, When they view any page, Then no No Fee Campaign components are rendered and no related code is loaded
User Story 2 - Feature Code is Properly Lazy-Loaded (Priority: P1)
The No Fee Campaign feature code should only be loaded when the feature flag is enabled for the current chain. When disabled, no feature code should be included in the initial bundle.
Why this priority: Bundle size optimization is a core benefit of the feature architecture. Disabled features should not impact application performance or initial load time.
Independent Test: Can be fully tested by building the application with the feature disabled and verifying that No Fee Campaign code is in a separate chunk that is not loaded initially. This delivers the value of improved performance for users on chains without the feature.
Acceptance Scenarios:
- Given the application is built, When the feature flag is disabled for a chain, Then No Fee Campaign code is in a separate code-split chunk
- Given a user on a chain with the feature disabled, When they load the application, Then the No Fee Campaign chunk is not downloaded
- Given a user on a chain with the feature enabled, When they navigate to a page using the feature, Then the feature chunk loads on-demand
- Given the application bundle is analyzed, When the feature is disabled, Then No Fee Campaign components and services are not included in the main bundle
User Story 3 - Feature Follows Architecture Standards (Priority: P1)
The migrated No Fee Campaign feature must follow all patterns defined in the feature architecture documentation, including proper contract definition, feature handle creation, hook exports, and public API structure.
Why this priority: Consistency with architecture standards ensures maintainability, enables proper tooling support (ESLint rules), and makes the codebase easier for developers to understand and contribute to.
Independent Test: Can be fully tested by verifying that the feature has the required files (contract.ts, feature.ts, index.ts), follows naming conventions, exports hooks correctly, and passes ESLint rules. This delivers the value of code consistency and maintainability.
Acceptance Scenarios:
- Given the feature is migrated, When ESLint runs, Then no restricted import warnings are generated for No Fee Campaign
- Given a developer imports the feature, When they use useLoadFeature with the feature handle, Then they receive proper TypeScript type inference
- Given the feature structure is reviewed, When checked against architecture documentation, Then all required files exist and follow the correct patterns
- Given hooks are accessed, When imported directly from the feature index, Then they work correctly without lazy loading violations
Edge Cases
- What happens when the feature flag is undefined (loading state) during initial render? The feature renders nothing until the flag resolves to true or false.
- How does the system handle errors when loading the feature chunk fails? Errors are logged via the error logging service and exposed via the
$errormeta property on the feature object returned byuseLoadFeature(). Components can checkfeature.$errorto handle error states appropriately. - What happens when eligibility data is loading but the feature is enabled? The
useNoFeeCampaignEligibilityhook maintains its existing loading state behavior (isLoading: true). Components can check this state and show loading skeletons. The feature architecture does not change this behavior - eligibility data loading is independent of feature code loading. - How does the system handle rapid toggling of the feature flag? The
useLoadFeaturehook reacts to flag changes via React's reactivity. When the flag changes fromtruetofalse, components return stub proxies (render null). When it changes back totrue, the feature chunk reloads. TheuseAsynchook inuseLoadFeaturehandles this reactively based on theisEnableddependency. - What happens when a user switches chains mid-session and the feature availability changes? When the chain changes,
useHasFeaturereturns a new value for the feature flag. TheuseLoadFeaturehook detects this change (via theisEnableddependency) and reacts accordingly: if the feature becomes disabled, components return stubs; if it becomes enabled, the feature chunk loads on-demand. The existing eligibility hook (useNoFeeCampaignEligibility) will also re-fetch data for the new chain. - How does the system handle blocked addresses that make users ineligible? This is maintained by existing logic in
useNoFeeCampaignEligibilityhook (FR-010). When a blocked address is detected, the hook returnsisEligible: falseandblockedAddressproperty. Components check this and display theBlockedAddresscomponent. The feature architecture does not change this behavior. - What happens when gas limits are exceeded for a transaction that would otherwise be eligible? The
useGasTooHighhook checks if gas exceedsMAX_GAS_LIMIT_NO_FEE_CAMPAIGN. When gas is too high, the execution method selector disables the No Fee Campaign option and shows theGasTooHighBannercomponent. This behavior is preserved (FR-011) - the feature architecture does not change the gas limit checking logic.
Requirements (mandatory)
Functional Requirements
- FR-001: System MUST maintain 100% functional parity with the current No Fee Campaign implementation after migration
- FR-002: System MUST lazy-load No Fee Campaign code only when the feature flag is enabled for the current chain
- FR-003: System MUST export a feature handle using
createFeatureHandle('no-fee-campaign')without the second parameter, relying on the existing semantic mapping toFEATURES.NO_FEE_NOVEMBER - FR-004: System MUST define a feature contract in
contract.tswith flat structure including all three components (NoFeeCampaignBanner, NoFeeCampaignTransactionCard, GasTooHighBanner) and any services (components and services only, no hooks) - FR-005: System MUST export all three hooks directly from
index.ts(useIsNoFeeCampaignEnabled, useNoFeeCampaignEligibility, useGasTooHigh) - not in contract or feature.ts - to avoid Rules of Hooks violations - FR-006: System MUST use direct imports in
feature.ts(no nested lazy loading) since the entire feature is already lazy-loaded - FR-007: System MUST organize components, hooks, services, and constants in appropriate subdirectories
- FR-008: System MUST update all consumer code to use
useLoadFeature()with the feature handle instead of direct imports - FR-009: System MUST ensure all No Fee Campaign components render null when the feature is disabled (via proxy stubs)
- FR-010: System MUST maintain all existing eligibility checking logic, including blocked address detection
- FR-011: System MUST preserve all existing UI states (loading, error, eligible, not eligible, limit reached, gas too high)
- FR-012: System MUST ensure hooks can be imported directly and work independently of feature loading state
- FR-013: System MUST pass ESLint restricted import rules (no imports from internal feature folders)
- FR-014: System MUST maintain type safety with proper TypeScript interfaces and contract types
- FR-015: System MUST ensure the feature flag check uses
FEATURES.NO_FEE_NOVEMBERvia the semantic mapping increateFeatureHandle(mapping already exists:'no-fee-campaign': FEATURES.NO_FEE_NOVEMBER)
Key Entities (include if feature involves data)
- No Fee Campaign Eligibility: Represents whether a Safe address is eligible for sponsored transactions, including remaining count, limit, and blocked address status
- No Fee Campaign Feature Handle: Runtime object containing feature name, flag check hook, and lazy load function for the feature implementation
- No Fee Campaign Contract: TypeScript interface defining the public API surface (components and services) exposed by the feature
- No Fee Campaign Implementation: The actual feature code (components, services) that is lazy-loaded when the feature is enabled
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: All existing No Fee Campaign functionality works identically after migration (100% feature parity verified through automated tests)
- SC-002: No Fee Campaign code is code-split into a separate chunk that loads only when the feature flag is enabled (verified through bundle analysis)
- SC-003: ESLint restricted import rules pass with zero warnings for No Fee Campaign feature imports (verified through linting)
- SC-004: All TypeScript type checks pass with full type inference working for feature handle usage (verified through type-check)
- SC-005: Bundle size for main chunk decreases when No Fee Campaign is disabled (measured through build analysis, target: remove No Fee Campaign code from main bundle)
- SC-006: All existing unit tests and integration tests for No Fee Campaign continue to pass after migration (100% test pass rate)
- SC-007: Feature can be disabled per chain without loading any feature code (verified through network tab inspection)
- SC-008: Developers can successfully use the feature via
useLoadFeature()pattern with proper autocomplete and type safety (verified through developer testing)
Assumptions
- The feature flag
FEATURES.NO_FEE_NOVEMBER(or equivalent) already exists in the chain configuration system - All existing No Fee Campaign components, hooks, and services can be organized into the feature architecture without breaking changes to their internal logic
- The migration will maintain backward compatibility during the transition (no breaking changes to external APIs)
- ESLint rules for restricted imports are already configured in the project
- The
createFeatureHandlehelper function is available and working correctly - All consumer code locations have been identified and can be updated in this migration
- No new functionality will be added during this migration (pure refactoring task)
Dependencies
- Feature architecture infrastructure (
@/features/__core__) must be available createFeatureHandlehelper function must be implemented and working- ESLint configuration must support restricted import rules
- Feature flag system (
useHasFeature,FEATURESenum) must be functional - Existing No Fee Campaign backend API endpoints and data structures remain unchanged
Out of Scope
- Adding new No Fee Campaign functionality or features
- Changing the eligibility logic or business rules
- Modifying the UI/UX design of No Fee Campaign components
- Updating backend APIs or data structures
- Adding new tests beyond what's needed to verify migration success
- Performance optimizations beyond what the architecture provides (lazy loading)
- Mobile app changes (this migration is web-only)