Skip to content

Milestones

List view

  • No due date
    8/8 issues closed
  • # Sprint 2 — E2E Utils# Sprint 2 — E2E Utils (Factory-Based Architecture) **Status**: 🔜 PLANNED **Status**: 🔜 PLANNED **Duration**: 2 weeks **Duration**: 2 weeks **Sprint Goal**: Ship kernel-aware E2E utilities that complement WordPress's Playwright fixtures with memorable, typed wrappers**Sprint Goal**: Ship `@geekist/wp-kernel-e2e-utils` with factory-based utilities that **complement** WordPress's Playwright fixtures while exposing all underlying primitives ---**Core Philosophy**: **Annotate & Expose, Never Hide** — Wrap WordPress utilities with kernel-aware factories while always exposing the underlying fixtures for power users. ## Goal--- Extend `@wordpress/e2e-test-utils-playwright` with kernel-aware utilities for testing resources, stores, and events. Utilities work with our existing kernel primitives (`defineResource`, store keys, event taxonomy) without duplicating WordPress functionality.## Goal ---Provide a stable, production-ready E2E testing package that extends `@wordpress/e2e-test-utils-playwright` with kernel-aware capabilities through composable factory functions. Developers should be able to test resources, stores, and events without duplicating WordPress utilities or breaking when WordPress updates. ## Scope--- **In Scope:**## Scope - Kernel-aware utilities: `seed()`, `waitForStore()`, `captureEvents()`**What's In Scope:** - Fixture extension: `test` with `kernel` helper exposing WordPress fixtures + kernel utilities - Works with existing kernel code: `defineResource`, `job.storeKey`, `wpk.*` events- **Factory Functions**: `createResourceUtils`, `createStoreUtils`, `createEventCapture` (foundation only) - Full TypeScript typing with generics- **Fixture Extension**: Extend WordPress's `test` fixture with `kernel` helper that exposes: - Factory methods for common operations (90% usage) **Out of Scope:** - All WordPress fixtures (`requestUtils`, `admin`, `editor`, `pageUtils`, `page`) - **TypeScript Generics**: Fully typed factories with generic type inference - Policy testing (Sprint 3), Action testing (Sprint 4), Job polling (Sprint 8)- **Tests**: Unit tests for factories, integration tests with WordPress utils - Database snapshots, visual regression, performance benchmarking- **Docs**: Factory API reference, usage examples, "Creating Custom Factories" guide ---**What's Out of Scope (Deferred):** ## User Stories- Full event taxonomy validation — **Sprint 4** (Actions & Events) - Policy testing utilities — **Sprint 3** (Policies) 1. **As a developer**, I can seed resource data using `seed(requestUtils, resource, data)` instead of raw REST calls- Job polling utilities — **Sprint 8** (Jobs) 2. **As a developer**, I can wait for store data with `waitForStore(page, storeKey, selector)` without polling loops- PHP bridge event testing — **Sprint 9** (PHP Bridge) 3. **As a developer**, I can capture events with `captureEvents(page)` and assert on kernel event emission- Visual regression testing — **Sprint 17** (Hardening) 4. **As a power user**, I can access WordPress's `requestUtils`, `admin`, `page` directly when needed --- --- ## User Stories ## Examples with Real Kernel Code 1. **As a developer**, I can seed resource data using a typed factory without writing raw REST calls ### Our Existing Kernel Primitives2. **As a developer**, I can wait for store data to load without polling loops 3. **As a developer**, I can capture and assert on kernel events for testing event-driven behavior ```typescript4. **As a power user**, I can drop down to WordPress's `requestUtils`or`page` when I need low-level control // From @geekist/wp-kernel5. **As a framework user**, I can create custom factories by extending the base factories without forking code const job = defineResource<Job>({ name: 'job',--- routes: { list: { path: '/wpk/v1/jobs', method: 'GET' },## Definition of Done create: { path: '/wpk/v1/jobs', method: 'POST' }, }### Package Deliverables });- ✅ Three core factories implemented: - `createResourceUtils` (seed, seedMany, remove, deleteAll) // Provides: - `createStoreUtils` (wait, invalidate, getState) job.create(data) // REST client method - `createEventCapture` (list, find, findAll, clear, stop) job.storeKey // 'wpk/job'- ✅ Fixture extends WordPress `test` with `kernel` helper job.invalidate() // Cache invalidation- ✅ All WordPress fixtures exposed through `kernel` (never hidden) // Events: wpk.resource.request, wpk.resource.response, etc.- ✅ TypeScript generics fully typed with inference ````- ✅ Zero dependencies on future sprints ### Example 1: Seed Resource Data### Testing - ✅ Unit tests for each factory (≥90% coverage) ```typescript- ✅ Integration tests showing factories working with WordPress utils import { test, expect } from '@geekist/wp-kernel-e2e-utils';- ✅ Type tests verifying generic inference - ✅ Example tests demonstrating all three usage styles test('seed jobs', async ({ kernel }) => { // Pass our actual resource config### Documentation const utils = kernel.resource(job);- ✅ Factory API reference with full signatures - ✅ "Annotate & Expose" philosophy explained await utils.seed({ title: 'Engineer', salary: 100000 });- ✅ Usage examples for each factory // Behind scenes: calls job.create() from our kernel- ✅ "Creating Custom Factories" guide - ✅ Migration examples from WordPress utils await utils.seedMany([- ✅ Three usage styles documented (fixture, direct, escape hatch) { title: 'Designer' }, { title: 'Manager' }### Quality ]);- ✅ Zero TypeScript errors });- ✅ Tree-shaking verified ```- ✅ No WordPress util duplication - ✅ Performance benchmark: < 100ms fixture overhead per test ### Example 2: Wait for Store- ✅ CI green (all tests passing) ```typescript--- test('wait for jobs', async ({ kernel }) => { await kernel.page.goto('/wp-admin/admin.php?page=wpk-jobs');## Architecture Overview // Use our actual storeKey### Philosophy: Annotate & Expose, Never Hide const store = kernel.store(job.storeKey); // 'wpk/job' ```typescript const jobs = await store.wait(s => s.getList());// ❌ Wrong: Hide WordPress utilities behind abstraction expect(jobs.items.length).toBeGreaterThan(0);kernel: { }); doThing() { /* uses requestUtils internally */ } ```} ### Example 3: Capture Events// ✅ Right: Expose everything, provide convenience layer kernel: { ```typescript // Factory methods for common operations (90% usage) test('capture kernel events', async ({ kernel }) => { resource: (config) => createResourceUtils(config, requestUtils), const recorder = await kernel.events({ pattern: /^wpk\./ }); store: (storeName) => createStoreUtils(storeName, page), events: (opts?) => createEventCapture(page, opts), await kernel.page.click('[data-testid="create-job"]'); await kernel.page.fill('[name="title"]', 'New Job'); // Full access to WordPress fixtures (always available) await kernel.page.click('[data-testid="submit"]'); requestUtils, // ✅ WordPress REST utilities admin, // ✅ WordPress admin navigation // Assert on our actual events editor, // ✅ WordPress block editor expect(recorder.find('wpk.resource.request')).toBeTruthy(); pageUtils, // ✅ WordPress page utilities expect(recorder.find('wpk.resource.response')).toBeTruthy(); page, // ✅ Playwright page } await recorder.stop();``` }); ```### Fixture Extension Pattern ### Example 4: Power User - Direct Access```typescript // src/fixture.ts ```typescriptimport { test as base } from '@wordpress/e2e-test-utils-playwright'; test('custom operation', async ({ kernel }) => {import { createResourceUtils, createStoreUtils, createEventCapture } from './factories'; // Drop down to WordPress utils when needed await kernel.requestUtils.rest({export const test = base.extend({ path: '/wpk/v1/jobs/batch-import', kernel: async ({ page, requestUtils, admin, editor, pageUtils }, use) => { method: 'POST', await use({ data: { source: 'csv', rows: [...] } // Factory methods }); resource: <T>(config: ResourceConfig<T>) => createResourceUtils(config, requestUtils), // Or use page directly store: (storeName: string) => await kernel.page.evaluate(() => { createStoreUtils(storeName, page), window.wp.data.dispatch('wpk/job').receiveItems([...]); events: (opts?: EventCaptureOptions) => }); createEventCapture(page, opts), }); ``` // Expose all WordPress fixtures requestUtils, --- admin, editor, ## Architecture pageUtils, page, ### Fixture Extension }); } ```typescript}); // src/fixture.ts import { test as base } from '@wordpress/e2e-test-utils-playwright';export { expect } from '@wordpress/e2e-test-utils-playwright'; ```` export const test = base.extend({ kernel: async ({ page, requestUtils, admin, editor, pageUtils }, use) => {--- await use({ // Kernel utilities (wraps our defineResource, storeKey, events)## Factory Implementations resource: (resourceObj) => createResourceUtils(resourceObj, requestUtils), store: (storeKey) => createStoreUtils(storeKey, page),### 1. createResourceUtils events: (opts?) => createEventCapture(page, opts), **Purpose**: Generic CRUD operations for any kernel resource // WordPress fixtures (always exposed) requestUtils,**Interface**: admin,```typescript editor,interface ResourceConfig<T> { pageUtils, name: string; page, routes: { }); list?: { path: string; method: 'GET' }; } create?: { path: string; method: 'POST' }; }); remove?: { path: string; method: 'DELETE' }; `````// ... other routes }; ### Utility Implementations} **`createResourceUtils(resourceObj, requestUtils)`**function createResourceUtils<T>( - Takes our `defineResource` output (job, application, etc.) config: ResourceConfig<T>, - Returns: `seed()`, `seedMany()`, `remove()`, `deleteAll()` requestUtils: RequestUtils - Uses `resourceObj.create()`, `resourceObj.remove()` internally): { seed(data: Partial<T>): Promise<T>; **`createStoreUtils(storeKey, page)`** seedMany(rows: Partial<T>[]): Promise<T[]>; - Takes our `job.storeKey` ('wpk/job') remove(id: string | number): Promise<void>; - Returns: `wait()`, `invalidate()`, `getState()` deleteAll(): Promise<void>; - Polls `select(storeKey).getList()` until data appears requestUtils: RequestUtils; // Exposed for advanced usage } **`createEventCapture(page, opts)`**``` - Captures `wpk.*` events from our kernel - Returns: `list()`, `find()`, `findAll()`, `clear()`, `stop()`**Usage**: - Injects listener via `wp.hooks.addAction('all', ...)````typescript test('seed jobs', async ({ kernel }) => { --- const job = kernel.resource<Job>({ name: 'job', ## Why This Approach routes: { list: { path: '/wpk/v1/jobs', method: 'GET' }, | Aspect | Benefit | create: { path: '/wpk/v1/jobs', method: 'POST' }, |--------|---------| remove: { path: '/wpk/v1/jobs/:id', method: 'DELETE' }, | **Works with kernel code** | Uses `defineResource`, `storeKey`, events we already have | }, | **Memorable names** | `seed()`, `waitForStore()` vs raw `requestUtils.rest()` | }); | **WordPress compatible** | Extends fixtures, doesn't replace them | | **Future-proof** | Same pattern for Actions (Sprint 4), Jobs (Sprint 8) | await job.seed({ title: 'Engineer', salary: 100000 }); | **Power user friendly** | All WordPress fixtures exposed | await job.seedMany([{ title: 'Designer' }, { title: 'Manager' }]); await job.deleteAll(); ---}); ````` ## Definition of Done --- ### Package Deliverables - ✅ `createResourceUtils`, `createStoreUtils`, `createEventCapture` implemented### 2. createStoreUtils - ✅ Fixture extends WordPress `test` with `kernel` helper - ✅ Works with existing `defineResource` output**Purpose**: Wait for and interact with `@wordpress/data` stores - ✅ All WordPress fixtures exposed (never hidden) - ✅ TypeScript generics for resource types**Interface**: ```typescript ### Testinginterface StoreWaitOptions { - ✅ Unit tests for each utility (≥90% coverage) timeoutMs?: number; - ✅ Integration tests with actual kernel resources intervalMs?: number; - ✅ Type tests verifying inference with `defineResource`} - ✅ Example tests in showcase app function createStoreUtils(storeName: string, page: Page): { ### Documentation wait<T>(selector: (store: any) => T, opts?: StoreWaitOptions): Promise<T>; - ✅ API reference: `kernel.resource()`, `kernel.store()`, `kernel.events()` invalidate(keys: unknown[]): Promise<void>; - ✅ Examples with real kernel code (job resource) getState(): Promise<any>; - ✅ "Annotate & Expose" philosophy page: Page; // Exposed for advanced usage - ✅ Migration from raw WordPress utils} ``` ### Quality - ✅ Zero TypeScript errors**Usage**: - ✅ Performance: < 100ms fixture overhead```typescript - ✅ CI green on all teststest('wait for store data', async ({ kernel }) => { await kernel.page.goto('/wp-admin/admin.php?page=wpk-jobs'); --- const jobStore = kernel.store('wpk/job'); ## Timeline (2 Weeks) const jobs = await jobStore.wait( ### Week 1 (store) => store.getList(), **Days 1-3: Utilities** { timeoutMs: 3000 } - `createResourceUtils` with `defineResource` integration ); - `createStoreUtils` with store key polling - `createEventCapture` with hook injection expect(jobs.items.length).toBeGreaterThan(0); }); **Days 4-5: Fixture**``` - Extend WordPress `test` fixture - Wire up utilities to `kernel` helper--- - Ensure all WordPress fixtures exposed ### 3. createEventCapture ### Week 2 **Days 6-8: Testing & Docs\*\***Purpose\*\*: Capture and assert on kernel events (foundation for Sprint 4) - Unit tests for utilities - Integration tests with kernel resources**Interface**: - API reference and examples```typescript interface EventCaptureOptions { **Days 9-10: Polish** pattern?: RegExp; - Migrate showcase tests includePayload?: boolean; - Performance benchmarks} - Final review interface EventRecord { --- name: string; payload?: unknown; ## Future Extensions timestamp: number; } Same pattern for upcoming sprints: async function createEventCapture( ```typescript page: Page, // Sprint 3: Policies opts?: EventCaptureOptions kernel.policy(policyConfig).check(user, resource);): Promise<{ list(): Promise<EventRecord[]>; // Sprint 4: Actions find(name: string): Promise<EventRecord | undefined>; kernel.action(actionConfig).invoke(params); findAll(pattern: RegExp): Promise<EventRecord[]>; clear(): Promise<void>; // Sprint 8: Jobs stop(): Promise<void>; kernel.job(jobConfig).enqueue(data).wait();}> ``` ---**Usage**: ````typescript ## Success Metricstest('capture resource events', async ({ kernel }) => { const recorder = await kernel.events({ pattern: /^wpk\.job\./ }); - **3 utilities** shipped: resource, store, events - **Works with kernel** - `defineResource`, `storeKey`, `wpk.*` events await kernel.page.click('[data-testid="create-job"]'); - **≥90% test coverage** for utilities await kernel.page.fill('[name="title"]', 'New Job'); - **100% WordPress fixtures** exposed await kernel.page.click('[data-testid="submit"]'); - **< 100ms overhead** per test const createdEvent = await recorder.find('wpk.job.created'); --- expect(createdEvent).toBeTruthy(); ## References await recorder.stop(); }); - [WordPress E2E Testing Guide](https://developer.wordpress.org/block-editor/contributors/code/testing-overview/e2e/)``` - [E2E Utils Package Specification](../E2E%20Utils%20Package%20Specification.md) - [Sprint 1 Retrospective](./sprint_1_retro.md)--- ---## Three Usage Styles **Next Sprint**: Sprint 3 — Policies (Client hints + Server parity)### 1. Via Fixture (Recommended) ```typescript import { test, expect } from '@geekist/wp-kernel-e2e-utils'; test('with fixture', async ({ kernel }) => { const job = kernel.resource(jobConfig); await job.seed({ title: 'Engineer' }); }); ```` ### 2. Direct Factory Import (Custom Setup) ```typescript import { createResourceUtils } from '@geekist/wp-kernel-e2e-utils'; test('direct', async ({ requestUtils }) => { const job = createResourceUtils(jobConfig, requestUtils); await job.seed({ title: 'Engineer' }); }); ``` ### 3. Drop Down to WordPress Utils (Escape Hatch) ```typescript test('escape hatch', async ({ kernel }) => { // Use WordPress utilities directly when needed await kernel.requestUtils.rest({ path: '/wpk/v1/custom-endpoint', method: 'POST', data: { custom: 'payload' } }); // Or manipulate page directly await kernel.page.evaluate(() => { window.wp.data.dispatch('wpk/job').receiveItems([...]); }); }); ``` --- ## Real-World Example: Full Workflow Test ```typescript import { test, expect } from '@geekist/wp-kernel-e2e-utils'; test('create and list jobs', async ({ kernel }) => { // Navigate using WordPress admin fixture (exposed through kernel) await kernel.admin.visitAdminPage('admin.php?page=wpk-jobs'); // Seed data using kernel resource factory const job = kernel.resource<Job>({ name: 'job', routes: { list: { path: '/wpk/v1/jobs', method: 'GET' }, create: { path: '/wpk/v1/jobs', method: 'POST' }, remove: { path: '/wpk/v1/jobs/:id', method: 'DELETE' }, }, }); await job.seedMany([ { title: 'Engineer', salary: 100000 }, { title: 'Designer', salary: 90000 }, ]); // Wait for store to update using kernel store factory const jobStore = kernel.store('wpk/job'); const jobs = await jobStore.wait((s) => s.getList()); expect(jobs.items).toHaveLength(2); // Cleanup await job.deleteAll(); }); ``` --- ## Package Structure ``` packages/e2e-utils/ ├── src/ │ ├── factories/ │ │ ├── createResourceUtils.ts │ │ ├── createStoreUtils.ts │ │ ├── createEventCapture.ts │ │ └── index.ts │ ├── fixture.ts # Extends WordPress test fixture │ └── index.ts # Main exports ├── tests/ │ ├── factories/ │ │ ├── resource.spec.ts │ │ ├── store.spec.ts │ │ └── events.spec.ts │ └── integration/ │ └── fixture.spec.ts ├── package.json └── README.md ``` --- ## Why Factory-Based vs Hardcoded Utilities? | Aspect | Hardcoded Utilities | Factory Functions | | ----------------------- | -------------------------- | --------------------------- | | **Maintenance** | 11+ utilities to maintain | 3 factories (composable) | | **WordPress Updates** | Manual sync required | Automatic compatibility | | **Extensibility** | Fixed utilities | Infinite via composition | | **Type Safety** | Per-util types | Generic types flow through | | **Bundle Size** | Larger (hardcoded) | Minimal (factory code) | | **Sprint Dependencies** | Depends on Sprint 4 events | No dependencies | | **User Customization** | Fork or submit PR | Create custom factory | | **Learning Curve** | 11 new utilities to learn | 3 factory patterns | | **Power User Access** | Limited | Full (all fixtures exposed) | --- ## Timeline (2 Weeks) ### Week 1 **Days 1-3: Core Factories** - Implement `createResourceUtils` with TypeScript generics - Implement `createStoreUtils` with wait/invalidate - Implement `createEventCapture` (foundation only) **Days 4-5: Fixture Integration** - Extend WordPress test fixture - Wire up factories to kernel fixture - Ensure all WordPress fixtures exposed - Write fixture integration tests ### Week 2 **Days 6-8: Testing & Documentation** - Unit tests for all factories (≥90% coverage) - Integration tests with WordPress utils - Type tests for generic inference - API reference documentation - Usage examples for all three styles **Days 9-10: Migration & Polish** - Migrate showcase E2E tests to new utilities - Performance benchmarks (< 100ms overhead) - Final documentation review - Changeset created - Ready for release --- ## Success Metrics ### Quantitative - **3 core factories** shipped - **≥90% test coverage** for factories - **100% fixture exposure** (no WordPress utils hidden) - **< 100ms overhead** per test from fixture - **Zero dependencies** on future sprints ### Qualitative - Developers can guess the API correctly - Power users can drop down to WordPress utils effortlessly - Custom factories can be created in < 30 lines of code - Tests feel like natural Playwright + WordPress patterns - No confusion about when to use factories vs WordPress utils --- ## Migration Path ### Phase 1: Core Factories (Sprint 2) - ✅ `createResourceUtils` - Generic resource operations - ✅ `createStoreUtils` - Store waiting/invalidation - ✅ `createEventCapture` - Foundation event capture - ✅ Fixture extension (exposes all WordPress fixtures) ### Phase 2: Enhanced Factories (Sprint 3+) - `createPolicyUtils` - Policy testing (Sprint 3) - `createActionUtils` - Action lifecycle testing (Sprint 4) - `createJobUtils` - Job polling/status (Sprint 8) ### Phase 3: Community Factories - Users create their own factories - Share via npm packages or gists - Framework remains minimal and focused --- ## Risks & Mitigations ### Risk: Complexity of Generic Types **Likelihood**: Medium **Impact**: Medium **Mitigation**: Start with simple types, add complexity iteratively. Provide comprehensive type examples in documentation. ### Risk: Event Capture Performance Overhead **Likelihood**: Low **Impact**: Medium **Mitigation**: Make payload capture opt-in. Benchmark with 1000+ events. Document performance characteristics. ### Risk: Breaking Changes from WordPress Updates **Likelihood**: Low **Impact**: Low **Mitigation**: We only extend, never fork. WordPress changes flow through automatically. Integration tests catch breaking changes. ### Risk: Users Don't Understand Factory Pattern **Likelihood**: Medium **Impact**: Medium **Mitigation**: Clear examples, comparison with old approach, "Creating Custom Factories" guide, video walkthrough. --- ## Dependencies ### Required (Sprint 1 Complete) - ✅ Resources implemented and stable - ✅ Store integration working - ✅ Resource events foundation (`wpk.resource.*`) - ✅ Showcase plugin exists with working tests ### Enables Future Sprints - **Sprint 3 (Policies)**: Test policy enforcement with `resource` factory and `requestUtils` - **Sprint 4 (Actions)**: Test action events with enhanced `createEventCapture` - **Sprint 8 (Jobs)**: Test job lifecycle with `createJobUtils` factory - **Sprint 9 (PHP Bridge)**: Test bridged events with event capture utilities --- ## Out of Scope (Future Sprints) - **Sprint 4**: Full event taxonomy validation, event payload schema checking - **Sprint 9**: PHP bridge event testing, sync/async execution testing - **Sprint 13**: Multi-version WordPress testing, flake detection - **Sprint 17**: Visual regression, performance benchmarking, accessibility testing --- ## References - [E2E Utils Package Specification](../E2E%20Utils%20Package%20Specification.md) - [Roadmap § Sprint 2](../Roadmap%20PO%20•%20v1.0.md) - [Sprint 1 Retrospective](./sprint_1_retro.md) - [WordPress E2E Utils](https://github.com/WordPress/gutenberg/tree/trunk/packages/e2e-test-utils-playwright) - [Playwright Fixtures Documentation](https://playwright.dev/docs/test-fixtures) --- **Next Sprint**: Sprint 3 — Policies (Client hints + Server parity)

    No due date
    8/8 issues closed
  • # Sprint 1.5 — Build Tooling & Resources Refactor **Status**: ✅ COMPLETE **Duration**: 1 day **Start Date**: October 1, 2025 **End Date**: October 2, 2025 **Sprint Goal**: Modernize build tooling with Vite and refactor kernel resources for better DX **Outcome**: Successfully delivered both major refactors (Issues #28 and #30). Achieved 2-3x faster builds, 78% smaller bundles, cleaner API surface, and maintained 100% test coverage. --- ## Overview Sprint 1.5 addressed two critical technical debt items that emerged after Sprint 1: 1. **Resources Refactor (#28)**: Simplified module structure, dual-surface API, better naming 2. **Vite Migration (#30)**: Modern build tooling, proper WordPress externalization, faster DX Both changes were necessary to unblock future development and improve developer experience. --- ## Goals ### Primary Goals 1. **Simplify Resource API**: Reduce cognitive load with clearer naming and flatter structure 2. **Modernize Build Tooling**: Replace TypeScript compiler with Vite for 2-3x faster builds 3. **Fix WordPress Externalization**: Properly externalize `@wordpress/*` packages (not bundle) 4. **Improve Module Resolution**: Remove painful `.js` extension requirements ### Secondary Goals - Maintain 100% test coverage (all existing tests must pass) - Reduce bundle size significantly - Prepare for Vitest migration (Phase 2) - Better tree-shaking with scoped imports --- ## Scope ### Issue #28: Kernel Resources Refactor **Problem Statement**: - Naming confusion: "transport" unclear, "resource" singular ambiguous - Deep nesting: 3 levels deep (`resource/store/createStore.ts`) - Verbose usage: Multiple imports for related operations - No hooks: Manual `useSelect()` required **Solution Delivered**: - **Clear Module Names**: `transport/` → `http/`, `errors/` → `error/` - **Flat Structure**: Max 2 levels deep, grouped concerns - **Dual-Surface API**: Thin-flat (everyday) + Grouped (power user) - **Three Import Patterns**: Scoped, namespace, flat aliases **Changes**: - Restructured `packages/kernel/src/` modules - Renamed files: `defineResource.ts` → `define.ts`, etc. - Consolidated cache utilities: `invalidate.ts` + `cacheKeys.ts` → `cache.ts` - Flattened store: `store/createStore.ts` → `store.ts` - Updated all imports across packages - Full API redesign (backwards compatible during transition) **Impact**: - +10,988 additions / -4,337 deletions - 383 unit tests passing (maintained coverage) - Cleaner import statements throughout codebase - Better discoverability for new developers ### Issue #30: Vite Build Tooling Migration **Problem Statement**: - `.js` extension hell in imports - Slow builds (15-20s with `tsc`) - WordPress packages accidentally bundled (100KB+ bundle) - No build-time verification of externals - Cross-package imports don't work in tests **Solution Delivered**: - **Vite Library Mode**: 2-3x faster builds - **Proper Externalization**: WordPress packages → `window.wp.*` - **No .js Extensions**: Clean imports - **Build Verification Tests**: Catch bundling issues - **Peer Dependencies**: Fixed `@kucrut/vite-for-wp` requirements **Changes**: - Installed Vite + plugins (vite, vite-plugin-dts, @kucrut/vite-for-wp) - Created `vite.config.base.ts` (shared library config) - Migrated all packages (kernel, ui, cli) to Vite - Removed `.js` extensions from 65 files - Deleted `webpack.config.cjs` from showcase - Added peer dependencies: `rollup-plugin-external-globals`, `vite-plugin-external` - Created build verification tests (4 new tests) - Updated CI for Vite builds **Impact**: - +1,954 additions / -212 deletions - 493 unit tests passing (+4 new tests) - 22KB bundle (was 100KB+) = 78% reduction - 2-3x faster builds (5-8s vs 15-20s) --- ## Issues Delivered ### ✅ Issue #28: Kernel Resources Refactor - **PR**: #29 - **Merged**: October 1, 2025 22:30 UTC - **Status**: Complete - **Key Deliverables**: - Simplified folder structure (http/, resource/, error/) - Dual-surface API (thin-flat + grouped) - Three import patterns (scoped, namespace, flat) - All 383 unit tests passing - API documentation updated ### ✅ Issue #30: Vite Build Tooling Migration (Phase 1) - **PR**: #32 - **Merged**: October 2, 2025 03:15 UTC - **Status**: Phase 1 Complete (Phase 2: Vitest migration deferred) - **Key Deliverables**: - Vite library mode for all packages - Proper WordPress externalization - Build verification tests - Peer dependencies fixed - 2-3x build performance improvement --- ## Technical Achievements ### Build Performance | Metric | Before | After | Improvement | | ----------- | ------ | ----- | ------------------ | | Cold Build | 15-20s | 5-8s | **2-3x faster** ✅ | | Incremental | ~5s | ~2s | **2.5x faster** ✅ | | Bundle Size | 100KB+ | 22KB | **78% smaller** ✅ | ### Code Quality - **Test Coverage**: 100% maintained (493/493 unit tests passing) - **Type Safety**: Full TypeScript coverage with generated .d.ts - **Tree Shaking**: Proper ESM with scoped imports - **Zero Console Errors**: In showcase app and E2E tests ### Developer Experience **Before (Sprint 1)**: ```typescript import { defineResource, invalidate } from '@geekist/wp-kernel' import { useSelect } from '@wordpress/data' const job = defineResource<Job>({ ... }) await job.list() // ✅ Works const items = useSelect(s => s(job.storeKey).getList()) // ❌ Verbose await invalidate(job.cacheKeys.list()) // ❌ Separate import ``` **After (Sprint 1.5)**: ```typescript import { resource } from '@geekist/wp-kernel' const job = resource.define<Job>({ ... }) const { data } = job.useList() // ✅ Built-in hook await job.invalidate(['job', 'list']) // ✅ Integrated ``` --- ## Architecture Changes ### New Module Structure ``` packages/kernel/src/ ├── http/ # ✅ Clear (was "transport") │ ├── fetch.ts │ └── types.ts ├── resource/ # ✅ Flat (was nested) │ ├── define.ts # ✅ Verb in folder │ ├── cache.ts # ✅ Grouped utilities │ ├── store.ts # ✅ No nesting │ └── types.ts └── error/ # ✅ Singular (was "errors") ├── KernelError.ts └── types.ts ``` ### Resource API Surface **Thin-Flat (11 core methods)**: - Reads: `get()`, `list()` - Hooks: `useGet()`, `useList()` - Writes: `create()`, `update()`, `remove()` - Cache: `prefetchGet()`, `prefetchList()`, `invalidate()` - Meta: `key()` **Grouped (power user)**: - `select.*` - Pure selectors - `fetch.*` - Explicit REST - `mutate.*` - Explicit writes - `cache.*` - Surgical control - `events.*` - Event names - `store.*` - Metadata ### Build Configuration **Shared Base Config** (`vite.config.base.ts`): - Vite library mode - WordPress externals via `@kucrut/vite-for-wp` - TypeScript declaration generation - Source maps - ESM output only **Per-Package Configs**: Extend base with package-specific entries --- ## Testing Strategy ### Test Coverage | Test Type | Count | Status | | ------------------ | -------- | ------------------------------------------- | | Unit Tests | 493 | ✅ 100% passing | | Integration Tests | Full MSW | ✅ Passing | | E2E Tests | 25 | ✅ Passing (27 total, 2 pre-existing flaky) | | Build Verification | 4 | ✅ New tests added | ### Build Verification Tests **New test suite** (`app/showcase/__tests__/build-verification/`): 1. `peer-dependencies.test.ts` - Verifies required deps installed 2. `imports.test.ts` - Validates import patterns 3. Runtime verification in E2E tests ### Critical Test Cases - ✅ WordPress packages not bundled (checked in build output) - ✅ Package exports work cross-package - ✅ TypeScript declarations generated - ✅ Showcase app loads without errors - ✅ Resource methods work in WordPress context --- ## Migration Impact ### Breaking Changes **None** - Both refactors maintained backwards compatibility during transition: - Old import patterns still work (flat aliases) - Build tooling change is transparent to consumers - All existing tests pass without modification ### Parked Work The following items were identified but deferred to future sprints: **From Issue #28** (documented in `Sprint 1.5 PARKED-future-work.md`): - Phase 4.2: Integration test updates (update method names in E2E) - Phase 5: Actions-First enforcement (ESLint rule + runtime guards) **From Issue #30**: - Phase 2: Vitest migration (Jest → Vitest) - Phase 3: Vite for showcase app (currently uses Vite, but can be further optimized) **Reason for Parking**: These are enhancements that don't block core functionality. Sprint 1.5 focused on critical infrastructure changes. --- ## Blockers Resolved ### Critical Bug: WordPress Data Store Access **Problem**: Showcase plugin couldn't access WordPress data stores after Vite migration. Bundle was 100KB+ instead of 22KB. **Root Cause**: Missing peer dependencies for `@kucrut/vite-for-wp`: - `rollup-plugin-external-globals@^0.13` - `vite-plugin-external@^6` **Solution**: 1. Added peer dependencies to `app/showcase/package.json` 2. Created build verification test to catch future issues 3. Updated documentation with requirements **Outcome**: WordPress packages properly externalized, bundle size reduced 78%. --- ## Success Criteria Met ### Issue #28 (Resources Refactor) - ✅ Simplified folder structure (2 levels max) - ✅ Clear module names (http, resource, error) - ✅ Dual-surface API implemented - ✅ Three import patterns working - ✅ All 383 unit tests passing - ✅ Documentation updated ### Issue #30 (Vite Migration) - ✅ All packages build with Vite - ✅ No `.js` extensions in imports - ✅ WordPress externals verified (not bundled) - ✅ Package exports work cross-package - ✅ All 493 unit tests pass - ✅ Build 2-3x faster - ✅ Bundle 78% smaller - ✅ Peer dependencies documented --- ## Documentation Updates ### Updated Documents - `app/showcase/README.md` - Build requirements - `app/showcase/vite.config.ts` - Inline comments - API documentation regenerated - Build verification test comments ### New Documents - `Sprint 1.5 PARKED-future-work.md` - Deferred items - `sprint 1.5 PLAN-modern-build-tooling.md` - Original plan - `sprint 1.5 kernel-resources-refactor.md` - Design doc --- ## Metrics ### Velocity - **Story Points**: 2 major issues completed - **Duration**: 1 day (October 1-2, 2025) - **PRs**: 2 merged (#29, #32) - **Files Changed**: 65 (refactor) + 67 (Vite) = 132 total - **Code Delta**: +12,942 / -4,549 ### Quality - **Test Coverage**: 100% maintained - **CI Success Rate**: 100% after fixes - **Breaking Changes**: 0 - **Regressions**: 0 ### Performance - **Build Time**: 2-3x faster - **Bundle Size**: 78% smaller - **Developer Experience**: Significantly improved --- ## Learnings ### What Worked Well 1. **Incremental Approach**: Two separate PRs allowed focused reviews 2. **Build Verification Tests**: Caught peer dependency issue early 3. **Documentation First**: Design docs clarified scope before coding 4. **Backwards Compatibility**: No breaking changes reduced risk ### Challenges 1. **Peer Dependencies**: `@kucrut/vite-for-wp` requirements not obvious 2. **CI Complexity**: Multiple build jobs required cache coordination 3. **Import Patterns**: Three patterns initially confusing (but powerful) ### Future Improvements 1. Add peer dependency checking to CI (prevent future issues) 2. Create migration guide for other projects 3. Consider Vitest migration in Sprint 2 4. Evaluate Actions-First enforcement tooling --- ## Next Steps ### Immediate - ✅ Merge PR #33 (E2E fixes) - ✅ Document Sprint 1.5 completion - ✅ Conduct retrospective ### Future Sprints - **Sprint 2**: Policies & permissions system - **Phase 2 (Deferred)**: Vitest migration - **Phase 5 (Deferred)**: Actions-First enforcement - **Future**: E2E test updates for new API surface --- ## References - **Issue #28**: [Kernel Resources Refactor](https://github.com/theGeekist/wp-kernel/issues/28) - **Issue #30**: [Vite Build Tooling Migration](https://github.com/theGeekist/wp-kernel/issues/30) - **PR #29**: [Resources Refactor](https://github.com/theGeekist/wp-kernel/pull/29) - **PR #32**: [Vite Migration](https://github.com/theGeekist/wp-kernel/pull/32) - **Design Docs**: - [Resources Refactor Plan](../sprint%201.5%20kernel-resources-refactor.md) - [Build Tooling Plan](../sprint%201.5%20PLAN-modern-build-tooling.md) - [Parked Work](../Sprint%201.5%20PARKED-future-work.md) --- **Sprint 1.5 Retrospective**: See [sprint_1.5_retro.md](./sprint_1.5_retro.md) **Previous Sprint**: [Sprint 1 - Resources & Stores](./sprint_1_resources_and_stores.md) **Next Sprint**: Sprint 2 - Policies & Permissions

    No due date
    2/2 issues closed
  • # Sprint 1 — Resources & Stores (V1 slice) **Status**: ✅ COMPLETE **Duration**: 1 week **Start Date**: September 30, 2025 **End Date**: October 1, 2025 **Sprint Goal**: Ship `defineResource` and the generated `@wordpress/data` store (selectors/resolvers), with request/response events and cache keys **Outcome**: Successfully delivered all 14 issues. Resource API working end-to-end with showcase plugin, full test coverage (465+ unit tests, 27 E2E tests), and comprehensive documentation. --- ## Goal Prove the Resource API end-to-end in the showcase plugin against a demo REST endpoint (`/wpk/v1/jobs`). Developers should be able to declare a resource with routes + schema and get a typed client + store they can select from in UI. --- ## Scope **What's In Scope:** - `packages/kernel`: `defineResource()` + transport layer wrapper (reuse `kernel.fetch`), request/response event emission (`wpk.resource.*`), cache key helpers, generated store (namespace `wpk/{resource}`), selectors/resolvers/actions - `examples/showcase-plugin/contracts/`: JSON Schema for demo Job Posting resource (`job.schema.json`) and type generation tooling - `examples/showcase-plugin`: Define Job Posting resource, create REST endpoints, render list page (admin route), show loading/error/empty states - **Tests**: Unit (types, cache keys, resolvers), integration (store with MSW), E2E (Playwright) - **Docs**: "Resources" guide + API reference showing how to use the framework **What's Out of Scope (Deferred):** - Mutations beyond `create()` (update/delete operations) - **Sprint 3** (Actions orchestration layer) - Policy/capability checks - **Sprint 2** - Jobs, Interactivity bundles - Later sprints - Server-side bindings - **Sprint 9** - PHP event bridge - **Sprint 8** --- ## User Stories 1. **As a developer**, I can declare a resource with routes + schema and get a typed client + store I can select from in UI 2. **As a developer**, I see request/response events fire for analytics and debugging 3. **As a developer**, I can invalidate cache keys deterministically and opt into `_fields` to trim payloads 4. **As a user**, I can open an admin page that lists demo items (from the tests site REST) and see loading/empty/error states --- ## Definition of Done - ✅ `defineResource()` works against demo REST endpoint (`/wpk/v1/jobs`) - ✅ Store exposes `getById`, `list` selectors - ✅ Store exposes `isResolving`, `hasResolved` meta selectors - ✅ Cache invalidation helper works (`invalidate(['job', 'list'])`) - ✅ `_fields` query parameter respected in REST calls when `route.fields` defined - ✅ Request/response events emit with correct payloads (`wpk.resource.request|response|error`) - ✅ Unit tests cover: - Cache key generation (deterministic) - Store selectors and resolvers - Error paths (network failure, 404, 500) - Event emission with `requestId` correlation - `_fields` parameter handling - ✅ E2E test: Admin page displays list from store (≥5 seeded jobs, no console errors) - ✅ Documentation updated: - API reference for `defineResource` - "Resources" guide with 3-step "Hello Resource" tutorial - Event payload schemas - ✅ Changeset created - ✅ CI green (all tests passing on 6.7 & latest matrix) --- ## Architecture Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Consumer Code │ │ const thing = defineResource({ name, routes, cacheKeys }) │ └────────────────────────────┬────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ defineResource() │ │ • Validates config │ │ • Generates typed client (list, get) │ │ • Registers @wordpress/data store │ │ • Wires up event emission │ └────────────────────────────┬────────────────────────────────┘ │ ┌────────────┴─────────────┐ ▼ ▼ ┌───────────────────────────┐ ┌──────────────────────────┐ │ Transport Layer │ │ Data Store │ │ • fetch() │ │ • Selectors │ │ • Correlation IDs │ │ • Resolvers │ │ • Error mapping │ │ • Actions │ │ • Event emission │ │ • Cache management │ └───────────┬───────────────┘ └─────────┬────────────────┘ │ │ ▼ ▼ ┌───────────────────────────────────────────────────────────┐ │ @wordpress/api-fetch │ │ @wordpress/data │ │ @wordpress/hooks │ └───────────────────────────────────────────────────────────┘ ``` --- ## Technical Specification ### `defineResource<T, Q>()` **Type Signature:** ```typescript type Route = { path: string; method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; fields?: string[] }; function defineResource<T, Q = Record<string, unknown>>({ name: string; routes: { list?: { path: string; method: 'GET'; fields?: string[] }; get?: { path: string; method: 'GET' }; create?: { path: string; method: 'POST' }; update?: { path: string; method: 'PUT' | 'PATCH' }; remove?: { path: string; method: 'DELETE' }; }; schema?: () => Promise<any>; cacheKeys: { list?: (q: Q | undefined) => Array<string | unknown>; get?: (id: number | string) => Array<string | unknown>; }; }): ResourceObject<T, Q> ``` **Example:** ```typescript // In examples/showcase-plugin/src/resources/job.ts import type { Job } from '@showcase/types/job'; const job = defineResource<Job, { q?: string }>({ name: 'job', routes: { list: { path: '/wpk/v1/jobs', method: 'GET', fields: ['id', 'title', 'status'], }, get: { path: '/wpk/v1/jobs/:id', method: 'GET' }, create: { path: '/wpk/v1/jobs', method: 'POST' }, }, schema: () => import('../contracts/job.schema.json'), cacheKeys: { list: (q: Q | undefined) => ['job', 'list', q], get: (id: number | string) => ['job', 'get', id], }, }); ``` **Returns:** ```typescript interface ResourceObject<T, Q> { client: { list(q?: Q): Promise<T[]>; get(id: number | string): Promise<T>; create(body: Partial<T>): Promise<T>; // Throws NotImplemented in Sprint 1 }; storeKey: string; // e.g., 'wpk/job' selectors: { getById(id): T | undefined; getList(q?): T[] | undefined; isResolving(selector, ...args): boolean; }; actions: { invalidate(keys: Array<string | unknown>): void; }; resolvers: { getById(id): Promise<T>; getList(q?): Promise<T[]>; }; } ``` **Events Emitted:** - `wpk.resource.request` - Before REST call - `wpk.resource.response` - After success - `wpk.resource.error` - On failure Payload includes: `{ resourceName, method, path, requestId, status, data? }` ### Store State Shape ```typescript type State<T> = { byId: Record<string | number, T>; lists: Record<string, Array<number | string>>; // keyed by JSON.stringify(q) resolving: Record<string, boolean>; errors: Record<string, KernelError | undefined>; }; ``` ### Transport Layer - Reuses Sprint 0 `kernel.fetch` wrapper (nonce + rootURL middleware) - Includes `_fields` query param when `route.fields` defined - Generates UUID v4 for `requestId` correlation --- ## Implementation Plan ### Phase 1: Foundation (Days 1-2) **1.1 Error Types** - `packages/kernel/src/errors/KernelError.ts` - Base error class - `packages/kernel/src/errors/TransportError.ts` - Network/HTTP errors - `packages/kernel/src/errors/ServerError.ts` - WordPress REST errors - `packages/kernel/src/errors/types.ts` - Error type definitions - Unit tests for error serialization **1.2 Transport Layer** - `packages/kernel/src/transport/fetch.ts` - Wrapper around `@wordpress/api-fetch` - Add correlation IDs (requestId) - Map HTTP errors to KernelError - Emit `wpk.resource.request` / `wpk.resource.response` - Support for `_fields` parameter - `packages/kernel/src/transport/types.ts` - Request/response types - Unit tests with mocked `@wordpress/api-fetch` **1.3 Event Registry** - `packages/kernel/src/events/registry.ts` - Canonical event names - `packages/kernel/src/events/types.ts` - Event payload types - `packages/kernel/src/events/emit.ts` - Helper for typed emission ### Phase 2: Resource Definition (Days 2-3) **2.1 Core API** - `packages/kernel/src/resource/defineResource.ts` - Main API - Accept config: name, routes, cacheKeys - Generate typed client methods - Return resource object with client - `packages/kernel/src/resource/types.ts` - Resource config types - `packages/kernel/src/resource/interpolate.ts` - Route param interpolation (`:id`) - Unit tests for route generation and param handling **2.2 Client Methods** - Implement `list(params)` - GET request with query params - Implement `get(id)` - GET request with ID interpolation - Stub `create()`, `update()`, `remove()` - throw "NotImplemented" with Sprint 3 reference - Unit tests for each method ### Phase 3: Store Generation (Days 3-4) **3.1 Store Factory** - `packages/kernel/src/resource/createStore.ts` - Generate @wordpress/data store - Selectors: `getById`, `list`, `getError`, `isResolving`, `hasResolved` - Resolvers: resolve `getById`, resolve `list` - Actions: `receiveItems`, `receiveItem`, `receiveError`, `invalidate` - `packages/kernel/src/resource/storeConfig.ts` - Store configuration builder - Unit tests with mocked `@wordpress/data` **3.2 Cache Key Management** - `packages/kernel/src/resource/cacheKeys.ts` - Cache key utilities - Generate keys from config - Match keys for invalidation - `packages/kernel/src/resource/invalidate.ts` - Global invalidation helper - Unit tests for cache key generation and matching ### Phase 4: Showcase Plugin - Job Posting Endpoints (Day 4) **4.1 PHP REST Controller** - `examples/showcase-plugin/includes/class-rest-controller.php` - Base controller - `examples/showcase-plugin/includes/rest/class-jobs-controller.php` - Job Postings endpoint - `GET /wpk/v1/jobs` - List with `_fields` support - `GET /wpk/v1/jobs/:id` - Single job posting - Stub POST/PUT/DELETE (return 501 Not Implemented, defer to Sprint 3) - Register routes in plugin bootstrap **4.2 Seed Data** - `examples/showcase-plugin/seeds/seed-jobs.sh` - Create 10 sample job postings - Add to `seed-all.sh` orchestration - Idempotent execution (skip if exists) ### Phase 5: Integration & Testing (Days 5-6) **5.1 Showcase Integration** - `examples/showcase-plugin/contracts/job.schema.json` - Job Posting schema (B1) - `examples/showcase-plugin/src/resources/job.ts` - Define job resource (B3) - `examples/showcase-plugin/src/components/JobsList.tsx` - Display job postings list (B4) - Register in plugin entry point - Build and test in wp-env **5.2 Unit Tests** - Transport layer (10+ tests) - Resource definition (15+ tests) - Store selectors/resolvers (20+ tests) - Cache key management (5+ tests) - Event emission (5+ tests) - **Target**: 60%+ coverage for new code **5.3 E2E Test** - `packages/e2e-utils/tests/sprint-1-resources.spec.ts` - Seeds job postings data - Visits admin page with JobsList component - Asserts list renders with ≥5 seeded job postings - Tests loading states - Tests error states (network failure simulation) ### Phase 6: Documentation (Day 6) **6.1 API Reference** - `docs/api/resources.md` - Complete `defineResource` API - All config options - Generated client methods - Store selectors/resolvers - Cache key patterns - Event emission contracts - Example using Job Posting resource from showcase **6.2 Usage Guide** - `docs/guide/resources.md` - "Creating Your First Resource" - Step-by-step tutorial using Job Posting as example - PHP endpoint setup in plugin - Schema definition in plugin contracts/ - Resource definition in plugin src/ - Component integration - Common patterns **6.3 Event Documentation** - Update `information/REFERENCE - Event Taxonomy Quick Card.md` - Add `wpk.resource.request` / `wpk.resource.response` payload examples --- ## Technical Decisions ### Read-Only for Sprint 1 **Decision**: Resource client only supports `list()` and `get()` in Sprint 1. Write operations return: ```typescript create() { throw new KernelError('NotImplemented', { message: 'Write operations require Actions (Sprint 3)', docs: 'https://theGeekist.github.io/wp-kernel/guide/actions' }); } ``` **Rationale**: - Actions-first rule is core to the architecture - Sprint 3 will add Actions to orchestrate writes - Prevents bad patterns from forming early - Clear error message guides developers to the Golden Path ### Store Registration **Decision**: Use `@wordpress/data.createReduxStore()` and register immediately when `defineResource()` is called. **Rationale**: - Matches Gutenberg patterns - Enables immediate selector usage - Simplifies testing (deterministic registration) ### Cache Keys as Functions **Decision**: Cache key config accepts functions, not static strings: ```typescript cacheKeys: { list: (params) => ['thing', 'list', params?.q, params?.cursor], get: (id) => ['thing', 'get', id] } ``` **Rationale**: - Dynamic keys based on params - Enables granular invalidation - Supports cursor pagination (Sprint 4+) ### Event Payload Structure **Decision**: All events include `requestId` for correlation: ```typescript { 'wpk.resource.request': { resourceName: string; method: string; path: string; params?: Record<string, unknown>; requestId: string; // UUID v4 } } ``` **Rationale**: - Enables request/response matching - Supports debugging and logging - Prepares for Reporter (Sprint 11) --- ## Dependencies **npm Packages** (already installed): - `@wordpress/api-fetch` - HTTP client - `@wordpress/data` - State management - `@wordpress/hooks` - Event system - `@wordpress/data-controls` - Async control flows **Internal**: - None (Sprint 1 is foundation) **External**: - PHP REST endpoint must exist before JS client works - wp-env must be running for E2E tests --- ## Risks & Mitigations ### Risk 1: @wordpress/data Complexity **Impact**: Medium | **Likelihood**: High The @wordpress/data API has a learning curve (controls, resolvers, selectors, thunks). **Mitigation**: - Study Gutenberg's `core/block-editor` store as reference - Start simple: basic selectors/resolvers without controls - Use `@wordpress/data-controls` only if needed for complex async - Write comprehensive unit tests early - Pair programming session for store implementation ### Risk 2: TypeScript Generics in defineResource **Impact**: Medium | **Likelihood**: Medium Generic types for `defineResource<T, Q>` could get complex with route inference. **Mitigation**: - Keep generics simple in v1 (explicit type params required) - Use `unknown` for params if needed; refine later - Add type tests to catch inference issues - Document type usage clearly with examples ### Risk 3: Seed Script Idempotency **Impact**: Low | **Likelihood**: Low Seed scripts must safely run multiple times without creating duplicates. **Mitigation**: - Already proven in Sprint 0 (users, jobs, applications work) - Follow same pattern for things (check before create) - Test idempotency explicitly in E2E ### Risk 4: Store Registration Timing **Impact**: Medium | **Likelihood**: Medium Store must register before components try to select from it. **Mitigation**: - Register stores in resource definition (immediate) - Test registration in unit tests - Add runtime check in selectors (dev warning if unregistered) - Document registration lifecycle in API docs --- ## Success Metrics ### Functional - ✅ Demo resource works end-to-end (seed → REST → store → UI) - ✅ All 5 DoD checkboxes complete - ✅ Zero manual steps to run demo ### Technical - ✅ 60%+ test coverage for new code - ✅ < 5KB gzipped for resource + store utilities - ✅ Store operations < 10ms (selector calls) - ✅ REST calls respect `_fields` (payload size optimization) ### DX (Developer Experience) - ✅ Clear error messages (e.g., "Write operations require Actions") - ✅ Full TypeScript autocomplete for resource client - ✅ API docs with copy-paste examples - ✅ Tutorial takes < 10 minutes to complete --- ## Open Questions ### Q1: Should defineResource be async? **Context**: JSON Schema import is async (`schema: import('./thing.schema.json')`) **Options**: 1. Make `defineResource` async, await schema load 2. Make `defineResource` sync, lazy-load schema on first validation 3. Remove schema from v1, add in Sprint 2 with Policies **Decision**: TBD (discuss in planning) **Recommendation**: Option 2 (sync definition, lazy schema) for better DX ### Q2: Where do cache keys live? **Context**: Invalidation needs to work across resources **Options**: 1. Global cache key registry (all resources register keys) 2. Store keys in resource config only (manual coordination) 3. Hybrid: local config, optional global registry **Decision**: TBD (discuss in planning) **Recommendation**: Option 3 (hybrid) for flexibility ### Q3: Error handling in resolvers? **Context**: What happens when REST call fails in resolver? **Options**: 1. Store error in state, return undefined from selector 2. Throw error from resolver (caught by @wordpress/data) 3. Return error object from selector **Decision**: TBD (discuss in planning) **Recommendation**: Option 1 (matches Gutenberg patterns) --- ## Sprint Retrospective Questions (Answer these at the end of Sprint 1) 1. Was the 1-week timeline realistic for this scope? 2. Did the phase breakdown help or hinder progress? 3. Which risks materialized? Were mitigations effective? 4. What surprised us (good or bad)? 5. What should we change for Sprint 2? 6. Did the DoD criteria work well? --- ## References - [Product Specification § 4.1](../Product%20Specification%20PO%20Draft%20%E2%80%A2%20v1.0.md#41-resources-model--client) - [Code Primitives § 5.1](../Code%20Primitives%20%26%20Dev%20Tooling%20PO%20Draft%20%E2%80%A2%20v1.0.md#51-transport) - [Event Taxonomy Quick Card](../REFERENCE%20-%20Event%20Taxonomy%20Quick%20Card.md) - [Sprint 0 Retrospective](./sprint_0_retro.md) - [Roadmap](../Roadmap%20PO%20Draft%20%E2%80%A2%20v1.0.md) --- ## Sprint Completion Summary ### Final Metrics - **Duration**: 2 days (September 30 - October 1, 2025) - **Issues Completed**: 14/14 (100%) - Phase A (Kernel Core): 6 issues (#1-#6) - Phase B (Showcase Integration): 4 issues (#7-#10) - Phase C (Testing): 4 issues (#11-#14) - **Test Coverage**: - Unit tests: 465+ passing - Integration tests: Full MSW coverage - E2E tests: 27 passing (Playwright) - **PRs Merged**: Multiple incremental PRs throughout sprint - **Documentation**: Complete (Resources guide, API reference, examples) ### Issues Delivered #### Phase A: Kernel Core - ✅ #1: A1: Result Types & Errors - ✅ #2: A2: defineResource Public API - ✅ #3: A3: Store Factory - ✅ #4: A4: Cache Invalidation Helper - ✅ #5: A5: \_fields Support - ✅ #6: A6: Docs (Resources) #### Phase B: Showcase Integration - ✅ #7: B1: JSON Schema + Types - ✅ #8: B2: REST Stub - ✅ #9: B3: Resource Declaration in Showcase - ✅ #10: B4: Admin Page (JobsList) #### Phase C: Testing - ✅ #11: C1: Unit Tests (Kernel) - ✅ #12: C2: Integration Tests (Kernel + MSW) - ✅ #13: C3: E2E (Playwright) - _Completed in follow-up PR #33_ - ✅ #14: C4: CI & Docs Hooks ### Key Outcomes **Technical Achievements**: - Resource API fully functional with type-safe client generation - WordPress Data store integration complete with selectors/resolvers - Event system operational (`wpk.resource.request`, `wpk.resource.response`, `wpk.resource.error`) - Cache invalidation system working with deterministic keys - `_fields` parameter support for payload optimization **Quality Metrics**: - All DoD criteria met - CI green (all test matrices passing) - Zero console errors in showcase app - Full documentation coverage - Changesets created for all features **Showcase Demo**: - Admin page displaying job listings from REST API - Loading/error/empty states properly handled - Real-world integration validated ### Blockers Resolved - **Issue #13 (E2E Tests)**: Initially had flaky failures, resolved in PR #33 with proper cache strategy and wait conditions ### Learnings - Incremental PR strategy worked well for complex features - MSW integration tests provided fast feedback loop - E2E tests require careful cache management in CI - Type generation from JSON Schema streamlined development --- **Sprint 1 Retrospective**: See [sprint_1_retro.md](./sprint_1_retro.md) **Next Sprint**: Sprint 1.5 - Build Tooling & Resources Refactor - See [sprint_1.5_build_and_refactor.md](./sprint_1.5_build_and_refactor.md)

    No due date
    15/15 issues closed