From 414eb709b9aa11ddece66e41d8dbd1f1c4e70755 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Sun, 7 Dec 2025 02:09:53 +0100 Subject: [PATCH] Add implementation plan for Policy Explorer UX Upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 (Research): 6 key decisions to make - Server/Client composition pattern for initial data + search - Null filtering strategy (backend vs frontend) - JSON formatting approach (native vs syntax highlighter) - Badge color mapping for policy types - Route strategy (/search redirect vs replacement) - Sheet component state management Phase 1 (Design): API contracts and component design - getRecentPolicySettings() Server Action (new) - searchPolicySettings() extension with limit param - PolicyDetailSheet component (Shadcn Sheet) - PolicyTable component with click handlers - PolicySearchContainer client wrapper Phase 2 (Implementation): 44 tasks across 7 epics - Epic 1: Backend Server Actions (P1, T001-T005) - Epic 2: Detail Sheet component (P1, T006-T011) - Epic 3: Table & Search components (P1, T012-T018) - Epic 4: Page refactor (P1, T019-T024) - Epic 5: Navigation update (P2, T025-T029) - Epic 6: Visual improvements (P3, T030-T034) - Epic 7: Testing & validation (T035-T044) Constitution compliance: ✅ All checks pass Performance targets: <2s initial load, <300ms detail sheet, <2s search No database changes required - extends existing schema --- specs/003-policy-explorer-ux/plan.md | 450 +++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 specs/003-policy-explorer-ux/plan.md diff --git a/specs/003-policy-explorer-ux/plan.md b/specs/003-policy-explorer-ux/plan.md new file mode 100644 index 0000000..c9f936f --- /dev/null +++ b/specs/003-policy-explorer-ux/plan.md @@ -0,0 +1,450 @@ +# Implementation Plan: Policy Explorer UX Upgrade + +**Branch**: `003-policy-explorer-ux` | **Date**: 2025-12-07 | **Spec**: [spec.md](./spec.md) +**Status**: Planning Phase + +## Summary + +Transform the existing global policy search (`/search`) into a comprehensive Policy Explorer with improved UX: show 50 newest policies by default (no empty state), filter out null values automatically, add clickable rows with detail sheet for JSON formatting, improve visual hierarchy with policy type badges, and consolidate navigation. + +**Technical Approach**: Extend existing `searchPolicySettings` Server Action with optional `limit` parameter and null-value filtering. Create new `PolicyDetailSheet` component using Shadcn Sheet. Refactor search page to be a Server Component that loads initial data, with client components for interactive features (search, detail sheet). Replace `/search` route with `/policy-explorer` and update navigation. + +## Technical Context + +**Language/Version**: TypeScript 5.x strict mode +**Primary Dependencies**: Next.js 16.0.3 App Router, Drizzle ORM 0.44.7, Shadcn UI, NextAuth.js 4.24.13, date-fns 4.x +**Storage**: PostgreSQL (no schema changes required) +**Testing**: Manual testing + E2E tests with Playwright (if applicable) +**Target Platform**: Docker containers (standalone build), web browsers +**Project Type**: Web application (Next.js App Router) +**Performance Goals**: Initial load < 2s, detail sheet open < 300ms, search response < 2s +**Constraints**: Server-first architecture (Server Actions + Server Components), Azure AD multi-tenant auth, no client-side fetches +**Scale/Scope**: Multi-tenant SaaS, 1000+ policy settings per tenant, 100+ concurrent users + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- [x] Uses Next.js App Router with Server Actions (no client-side fetches) +- [x] TypeScript strict mode enabled (strict: true in tsconfig.json) +- [x] Drizzle ORM for all database operations (extends existing queries) +- [x] Shadcn UI for all new components (Sheet, Badge components) +- [x] Azure AD multi-tenant authentication (uses existing session) +- [x] Docker deployment with standalone build (no special config needed) + +**Constitution Compliance**: ✅ All requirements met - feature extends existing patterns + +## Project Structure + +### Documentation (this feature) + +```text +specs/003-policy-explorer-ux/ +├── spec.md # Feature specification (✅ completed) +├── plan.md # This file (implementation plan) +├── research.md # Phase 0 output (research findings) +├── data-model.md # Phase 1 output (no DB changes, documents filtering logic) +├── quickstart.md # Phase 1 output (developer setup guide) +├── contracts/ # Phase 1 output (API contracts) +│ └── server-actions.md # Server Action signatures +├── checklists/ +│ └── requirements.md # Specification quality checklist (✅ completed) +└── tasks.md # Phase 2 output (NOT created by this plan) +``` + +### Source Code (repository root) + +```text +# Existing files to modify +lib/ +├── actions/ +│ └── policySettings.ts # [MODIFY] Add getRecentPolicySettings(), extend searchPolicySettings() +└── db/ + └── schema/ + └── policySettings.ts # [NO CHANGE] Uses existing schema + +components/ +├── search/ +│ ├── SearchInput.tsx # [MODIFY] Add null-value filtering, refactor for new layout +│ ├── ResultsTable.tsx # [MODIFY] Add row click handler, hover styles, badge rendering +│ ├── EmptyState.tsx # [MODIFY] Update message for Policy Explorer context +│ └── SyncButton.tsx # [NO CHANGE] Keep existing sync functionality +└── ui/ + ├── sheet.tsx # [CHECK] Verify Shadcn Sheet installed, add if missing + └── badge.tsx # [CHECK] Verify Shadcn Badge installed, add if missing + +app/ +└── (app)/ + ├── search/ + │ └── page.tsx # [REFACTOR] Convert to Server Component with initial data load + └── policy-explorer/ + └── page.tsx # [NEW] New route (may redirect from /search or replace it) + +config/ +└── nav.ts # [MODIFY] Replace "Search" with "Policy Explorer", remove "All Settings" + +# New files to create +components/ +└── policy-explorer/ + ├── PolicyDetailSheet.tsx # [NEW] Slide-over detail view for policy settings + ├── PolicyTable.tsx # [NEW] Refactored table with click handlers and badges + └── PolicySearchContainer.tsx # [NEW] Client wrapper for search functionality +``` + +**Structure Decision**: Single Next.js project following existing App Router patterns. New components under `components/policy-explorer/` to distinguish from legacy `/search` components. Route can either replace `/search` or create new `/policy-explorer` route (decision in Phase 1). + +## Phase 0: Research & Unknowns Resolution + +**Status**: 🔄 In Progress + +### Research Questions + +#### R001: How to implement Server Component with initial data load + client search? +**Decision**: NEEDS CLARIFICATION +- **Option A**: Server Component page fetches initial 50 entries, passes to Client Component wrapper +- **Option B**: Separate Server Component for table + Client Component for search (parallel composition) +- **Research needed**: Next.js 16 patterns for mixing Server/Client with initial data + user interactions + +#### R002: How to filter null values - backend or frontend? +**Decision**: NEEDS CLARIFICATION +- **Option A**: Backend filter in Drizzle query (`ne(policySettings.settingValue, "null")`) +- **Option B**: Frontend filter after Server Action returns data +- **Option C**: Hybrid - backend for initial load, frontend for search results +- **Research needed**: Performance implications of filtering 1000+ records, indexing considerations + +#### R003: How to format JSON in detail sheet? +**Decision**: NEEDS CLARIFICATION +- **Option A**: Use `JSON.parse()` + `JSON.stringify(parsed, null, 2)` with `
` tag
+- **Option B**: Install syntax highlighter like `react-syntax-highlighter` or `prism-react-renderer`
+- **Option C**: Use browser-native `` with Tailwind prose styles
+- **Research needed**: Bundle size impact, accessibility of code blocks, whether syntax highlighting is needed for MVP
+
+#### R004: Badge color mapping for policy types?
+**Decision**: NEEDS CLARIFICATION
+- **Option A**: Hardcode mapping (Compliance=blue, Configuration=gray, Security=red)
+- **Option B**: Dynamic hashing (hash policyType string to generate consistent color)
+- **Option C**: Store colors in database (requires schema change - violates spec)
+- **Research needed**: Actual policy type values in production data, Shadcn Badge variant options
+
+#### R005: Should `/search` route redirect to `/policy-explorer` or be replaced?
+**Decision**: NEEDS CLARIFICATION
+- **Option A**: Keep both routes, redirect `/search` → `/policy-explorer` (backwards compatibility)
+- **Option B**: Replace `/search` entirely, update all internal links
+- **Option C**: Keep `/search` as-is for now, create `/policy-explorer` as new route (migration path)
+- **Research needed**: Check for external links to `/search` route, impact on bookmarks
+
+#### R006: How to handle Sheet component state with Server Actions?
+**Decision**: NEEDS CLARIFICATION
+- **Option A**: Client Component manages open/close state, fetches detail via Server Action on open
+- **Option B**: Pass full policy data to Sheet component on click (no additional fetch)
+- **Option C**: Use URL params for selected policy ID (deep linking support)
+- **Research needed**: Shadcn Sheet best practices, data freshness requirements
+
+### Research Deliverables
+- `research.md` documenting all decisions with rationale
+- Code examples for Server/Client composition pattern
+- Performance benchmarks for null-value filtering approaches
+- Badge color mapping specification
+
+## Phase 1: Design & Contracts
+
+**Status**: ⏳ Pending Phase 0 completion
+
+### Data Model
+
+**No database schema changes required**. Feature uses existing `policySettings` table:
+
+```typescript
+// Existing schema (no modifications)
+export const policySettings = pgTable('policy_settings', {
+  id: uuid('id').primaryKey().defaultRandom(),
+  tenantId: uuid('tenant_id').notNull(), // Multi-tenant isolation
+  policyName: text('policy_name').notNull(),
+  policyType: text('policy_type').notNull(),
+  settingName: text('setting_name').notNull(),
+  settingValue: text('setting_value').notNull(),
+  graphPolicyId: text('graph_policy_id'),
+  lastSyncedAt: timestamp('last_synced_at').notNull(),
+  createdAt: timestamp('created_at').notNull().defaultNow(),
+});
+```
+
+**Filtering Logic** (to be documented in `data-model.md`):
+- Null value detection: `settingValue === "null" || settingValue === null`
+- Applied at: [TBD in Phase 0 - backend vs frontend]
+- Performance impact: [TBD in Phase 0 - requires benchmarking]
+
+### API Contracts
+
+#### Server Action: `getRecentPolicySettings(limit?: number)`
+
+**File**: `lib/actions/policySettings.ts` (new function)
+
+```typescript
+export async function getRecentPolicySettings(
+  limit: number = 50
+): Promise {
+  // Returns: Recent policy settings, sorted by lastSyncedAt DESC
+  // Filters: tenantId (from session), exclude null values
+  // Security: Session validation, tenant isolation
+}
+```
+
+**Contract**:
+- **Input**: `limit` (optional, default 50, max 100)
+- **Output**: `{ success: boolean, data?: PolicySettingSearchResult[], error?: string, totalCount?: number }`
+- **Security**: Validates session, extracts tenantId, enforces tenant isolation
+- **Performance**: Database query with LIMIT clause, DESC index on lastSyncedAt
+
+#### Server Action: `searchPolicySettings(searchTerm: string, limit?: number)` [MODIFIED]
+
+**File**: `lib/actions/policySettings.ts` (extend existing function)
+
+**Changes**:
+- Add optional `limit` parameter (default 100, max 200)
+- Add null-value filtering (if backend approach chosen in Phase 0)
+- Keep existing security and tenant isolation logic
+
+#### Server Action: `getPolicySettingById(id: string)` [EXISTING]
+
+**File**: `lib/actions/policySettings.ts` (already exists, line ~164)
+
+**Usage**: Fetch full policy details for detail sheet (if on-click fetch approach chosen in Phase 0)
+
+### Component Contracts
+
+#### Component: `PolicyDetailSheet`
+
+**File**: `components/policy-explorer/PolicyDetailSheet.tsx` (new)
+
+**Props**:
+```typescript
+interface PolicyDetailSheetProps {
+  policy: PolicySettingSearchResult | null;
+  open: boolean;
+  onOpenChange: (open: boolean) => void;
+}
+```
+
+**Features**:
+- Shadcn Sheet component (slide-over from right)
+- JSON detection: `settingValue.trim().startsWith('{') || settingValue.trim().startsWith('[')`
+- JSON formatting: `JSON.stringify(JSON.parse(value), null, 2)` with error handling
+- Fallback: Display as plain text if JSON.parse fails
+
+#### Component: `PolicyTable`
+
+**File**: `components/policy-explorer/PolicyTable.tsx` (new, refactored from ResultsTable.tsx)
+
+**Props**:
+```typescript
+interface PolicyTableProps {
+  policies: PolicySettingSearchResult[];
+  onRowClick: (policy: PolicySettingSearchResult) => void;
+}
+```
+
+**Features**:
+- Badge rendering for `policyType` column
+- Row hover effect: `hover:bg-accent cursor-pointer`
+- Click handler on TableRow
+- Date formatting with `date-fns` (existing dependency)
+
+#### Component: `PolicySearchContainer`
+
+**File**: `components/policy-explorer/PolicySearchContainer.tsx` (new)
+
+**Props**:
+```typescript
+interface PolicySearchContainerProps {
+  initialPolicies: PolicySettingSearchResult[];
+}
+```
+
+**Features**:
+- Client Component wrapper ('use client')
+- Manages search state and detail sheet state
+- Calls `searchPolicySettings` Server Action on user input
+- Filters null values (if frontend approach chosen in Phase 0)
+- Renders SearchInput + PolicyTable + PolicyDetailSheet
+
+### Page Design
+
+#### Page: `/policy-explorer` (or refactored `/search`)
+
+**File**: `app/(app)/policy-explorer/page.tsx` (new) or `app/(app)/search/page.tsx` (refactor)
+
+**Composition**:
+```tsx
+export default async function PolicyExplorerPage() {
+  // Server Component - fetches initial data
+  const initialData = await getRecentPolicySettings(50);
+  
+  return (
+    
+ + + Policy Explorer + Browse and search Intune policy settings + {/* Keep existing sync button */} + + + + + +
+ ); +} +``` + +**Flow**: +1. Server Component fetches initial 50 policies (no search term) +2. Passes to Client Component as props +3. Client Component renders table with initial data +4. User searches → Client Component calls Server Action → Updates table +5. User clicks row → Client Component opens detail sheet + +### Navigation Update + +**File**: `config/nav.ts` + +**Changes**: +- Replace `{ title: "Search", href: "/search" }` with `{ title: "Policy Explorer", href: "/policy-explorer" }` +- Remove `{ title: "All Settings", href: "/settings-overview" }` (if exists) +- Keep icon (or change to relevant icon like FileSearch or Database) + +## Phase 2: Implementation Tasks + +**Status**: ⏳ Pending Phase 1 completion + +### Task Breakdown (High-Level) + +#### Epic 1: Backend - Server Actions (P1) +- [ ] T001: Add `getRecentPolicySettings()` Server Action with null filtering +- [ ] T002: Extend `searchPolicySettings()` with optional limit parameter +- [ ] T003: Add null-value filtering to search results (if backend approach chosen) +- [ ] T004: Add unit tests for Server Actions +- [ ] T005: Verify tenant isolation in new query + +#### Epic 2: Components - Detail Sheet (P1) +- [ ] T006: Install Shadcn Sheet component (if not present) +- [ ] T007: Install Shadcn Badge component (if not present) +- [ ] T008: Create `PolicyDetailSheet.tsx` with JSON detection logic +- [ ] T009: Create JSON formatting utility with error handling +- [ ] T010: Style Sheet component (width, padding, close button) +- [ ] T011: Test Sheet with various JSON structures (nested objects, arrays) + +#### Epic 3: Components - Table & Search (P1) +- [ ] T012: Create `PolicyTable.tsx` with row click handlers +- [ ] T013: Add Badge rendering for policyType column +- [ ] T014: Add hover styles and cursor pointer to rows +- [ ] T015: Create `PolicySearchContainer.tsx` Client Component wrapper +- [ ] T016: Implement search state management (useTransition) +- [ ] T017: Implement detail sheet state management (open/close) +- [ ] T018: Add null-value filtering (if frontend approach chosen) + +#### Epic 4: Page Refactor (P1) +- [ ] T019: Create or refactor page to Server Component pattern +- [ ] T020: Fetch initial 50 policies in Server Component +- [ ] T021: Pass initial data to Client Component +- [ ] T022: Update CardHeader title to "Policy Explorer" +- [ ] T023: Test page with no policies (empty state) +- [ ] T024: Test page with 50+ policies (initial load performance) + +#### Epic 5: Navigation & Routing (P2) +- [ ] T025: Update `config/nav.ts` with new route +- [ ] T026: Remove "All Settings" menu item (if exists) +- [ ] T027: Decide on /search redirect vs replacement +- [ ] T028: Implement redirect or route replacement +- [ ] T029: Update internal links to use new route + +#### Epic 6: Visual Improvements (P3) +- [ ] T030: Define policy type to badge color mapping +- [ ] T031: Implement badge color logic in PolicyTable +- [ ] T032: Test badge colors with production data +- [ ] T033: Add loading skeletons for table (optional) +- [ ] T034: Add empty state component for no results + +#### Epic 7: Testing & Validation (All Priorities) +- [ ] T035: Manual test - Initial load shows 50 policies +- [ ] T036: Manual test - Null values filtered correctly +- [ ] T037: Manual test - Row click opens detail sheet +- [ ] T038: Manual test - JSON formatted in detail sheet +- [ ] T039: Manual test - Search filters null values +- [ ] T040: Manual test - Badge colors correct +- [ ] T041: E2E test - Page load and search flow +- [ ] T042: E2E test - Detail sheet open/close +- [ ] T043: Performance test - Initial load < 2s +- [ ] T044: Performance test - Detail sheet < 300ms + +### Implementation Order +1. **Phase 2.1** (P1 Backend): T001-T005 (Server Actions) +2. **Phase 2.2** (P1 Components): T006-T011 (Detail Sheet) + T012-T018 (Table) +3. **Phase 2.3** (P1 Page): T019-T024 (Page refactor) +4. **Phase 2.4** (P2 Navigation): T025-T029 (Routing) +5. **Phase 2.5** (P3 Polish): T030-T034 (Visual improvements) +6. **Phase 2.6** (Testing): T035-T044 (Validation) + +## Known Constraints + +1. **No database migrations**: Feature must work with existing schema (✅ confirmed in spec) +2. **Server-first architecture**: All data fetching via Server Actions (✅ plan follows this) +3. **Backwards compatibility**: May need redirect from `/search` to `/policy-explorer` (TBD in Phase 1) +4. **Bundle size**: Must evaluate syntax highlighter libraries if chosen (TBD in Phase 0) +5. **Tenant isolation**: All queries must include tenantId filter (✅ existing pattern) + +## Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Null filtering on 1000+ records slow | High | Phase 0 benchmarking, backend index if needed | +| JSON.parse() fails on malformed data | Medium | Try/catch with fallback to plain text | +| Sheet component state management complex | Medium | Research Phase 0, follow Shadcn examples | +| Breaking change to `/search` route | Low | Implement redirect, communicate to users | +| Badge colors insufficient contrast | Low | Use Shadcn variants, test accessibility | + +## Success Criteria Mapping + +| Success Criterion (from spec) | Implementation | +|-------------------------------|----------------| +| SC-001: Initial load < 2s | Server Component with `getRecentPolicySettings(50)` + database indexes | +| SC-002: 100% valid values (no nulls) | Backend or frontend filtering (Phase 0 decision) | +| SC-003: Detail sheet < 300ms | Client-side state management, no additional data fetch | +| SC-004: JSON formatted correctly | `JSON.parse()` + `JSON.stringify()` with `
` tag |
+| SC-005: Badge colors distinguishable | Shadcn Badge variants, color mapping (Phase 1) |
+| SC-006: Single "Policy Explorer" menu | Update `config/nav.ts`, remove "All Settings" |
+
+## Deployment Checklist
+
+- [ ] All Phase 0 research documented in `research.md`
+- [ ] All Phase 1 contracts documented in `contracts/`
+- [ ] Code committed to `003-policy-explorer-ux` branch
+- [ ] Feature flag enabled (if applicable)
+- [ ] Navigation updated in `config/nav.ts`
+- [ ] Manual testing checklist completed
+- [ ] E2E tests passing (if implemented)
+- [ ] Performance benchmarks validated
+- [ ] Merge to `development` branch
+- [ ] Deploy to staging environment
+- [ ] User acceptance testing
+- [ ] Deploy to production
+- [ ] Monitor performance metrics
+- [ ] Gather user feedback
+
+## Next Steps
+
+1. **Immediate**: Complete Phase 0 research (resolve all NEEDS CLARIFICATION items)
+2. **After Phase 0**: Generate `research.md` with documented decisions
+3. **Phase 1**: Create `data-model.md`, `contracts/`, `quickstart.md`
+4. **Phase 2**: Run `/speckit.tasks` to generate detailed task breakdown
+5. **Implementation**: Execute tasks in priority order (P1 → P2 → P3)
+
+## References
+
+- Feature Spec: [spec.md](./spec.md)
+- Requirements Checklist: [checklists/requirements.md](./checklists/requirements.md)
+- Related Feature: [specs/001-global-policy-search/](../001-global-policy-search/) (base implementation)
+- Related Feature: [specs/002-manual-policy-sync/](../002-manual-policy-sync/) (SyncButton component)
+- Constitution: [.specify/memory/constitution.md](../../.specify/memory/constitution.md)
+- Shadcn UI Sheet Docs: https://ui.shadcn.com/docs/components/sheet
+- Shadcn UI Badge Docs: https://ui.shadcn.com/docs/components/badge