diff --git a/PRIVACY.md b/PRIVACY.md index e3880c8..621d453 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -70,7 +70,7 @@ request metadata such as your IP address and browser headers when those assets l ## Optional Explore video provider Explore's live Copilot dock only projects activity already present in the active -end-to-end encrypted session. Its bundled shuffled Discover deck, saved-card state, +end-to-end encrypted session. Its bundled shuffled Discover deck, reading progress, Play activities, Unwind activities, and scores remain local to the phone. If a deployment configures the optional Watch widget, Weft displays a disclosure and does not contact that provider until you explicitly choose to load the video feed. diff --git a/README.md b/README.md index 18d4902..c222d4c 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ while you use the phone. - **Explicit authority.** The phone receives the same approval boundaries exposed by the underlying Copilot session. Weft does not invent an automatic approval policy. - **Explore stays local by default.** The live Copilot dock projects data already present - in the active encrypted session, while the shuffled Discover deck, saved-card signals, + in the active encrypted session, while the shuffled Discover deck, reading progress, Play, Unwind, and scores remain on the phone. Attention states return to the canonical chat controls. The optional Watch provider loads only after an explicit tap and receives no Weft prompt, repository, filename, tool, or session context. diff --git a/mobile/src/ui/explore/ExploreScreen.tsx b/mobile/src/ui/explore/ExploreScreen.tsx index 7746582..13f93ea 100644 --- a/mobile/src/ui/explore/ExploreScreen.tsx +++ b/mobile/src/ui/explore/ExploreScreen.tsx @@ -1,7 +1,6 @@ import { useCallback, useEffect, - useLayoutEffect, useMemo, useRef, useState, @@ -83,7 +82,6 @@ const STORAGE_KEY = 'weft.explore.v1'; interface ExploreStoredState { lastCategory?: ExploreCategory; - savedCardIds?: string[]; completedCardIds?: string[]; discoverDay?: string; discoverCycle?: number; @@ -95,7 +93,6 @@ interface ExploreStoredState { const DEFAULT_STORED_STATE: Required = { lastCategory: 'discover', - savedCardIds: [], completedCardIds: [], discoverDay: '', discoverCycle: 0, @@ -117,11 +114,8 @@ function parseStoredState(): Required { parsed.lastCategory === 'unwind' ? parsed.lastCategory : DEFAULT_STORED_STATE.lastCategory; - return { + const state: Required = { lastCategory, - savedCardIds: Array.isArray(parsed.savedCardIds) - ? parsed.savedCardIds.filter((value): value is string => typeof value === 'string') - : [], completedCardIds: Array.isArray(parsed.completedCardIds) ? parsed.completedCardIds.filter((value): value is string => typeof value === 'string') : [], @@ -142,6 +136,11 @@ function parseStoredState(): Required { typeof parsed.signalBest === 'number' && parsed.signalBest >= 0 ? Math.floor(parsed.signalBest) : 0, vibration: parsed.vibration === true, }; + const legacyCardPreferenceKey = 'savedCardIds'; + if (legacyCardPreferenceKey in parsed) { + globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(state)); + } + return state; } catch { return DEFAULT_STORED_STATE; } @@ -324,20 +323,6 @@ function WaveGlyph(): JSX.Element { ); } -function BookmarkGlyph({ filled }: { filled: boolean }): JSX.Element { - return ( - - ); -} - export function ExploreScreen({ active, onOpenSessions, @@ -623,26 +608,7 @@ function DiscoverView({ ); const index = Math.min(persistedIndex, Math.max(0, deck.length - 1)); const card = deck[index]; - const saved = new Set(stored.savedCardIds); const pointerStart = useRef<{ x: number; y: number } | null>(null); - const viewRef = useRef(null); - const [scrollable, setScrollable] = useState(false); - - useLayoutEffect(() => { - const element = viewRef.current; - if (!element) return undefined; - const measure = (): void => { - setScrollable(element.scrollHeight > element.clientHeight + 1); - }; - measure(); - const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure); - observer?.observe(element); - window.addEventListener('resize', measure); - return () => { - observer?.disconnect(); - window.removeEventListener('resize', measure); - }; - }, [card.id]); useEffect(() => { if ( @@ -671,43 +637,34 @@ function DiscoverView({ onChange({ ...stored, completedCardIds: completed, discoverDay: today, discoverCycle: cycle + 1, discoverIndex: 0 }); }; - const toggleSaved = (): void => { - if (!card) return; - const next = new Set(saved); - if (next.has(card.id)) next.delete(card.id); - else next.add(card.id); - onChange({ ...stored, savedCardIds: [...next] }); - }; - const onPointerUp = (event: ReactPointerEvent): void => { const start = pointerStart.current; pointerStart.current = null; if (!start) return; const deltaX = event.clientX - start.x; const deltaY = event.clientY - start.y; - if (Math.abs(deltaY) < 48 || Math.abs(deltaY) <= Math.abs(deltaX) * 1.2) return; - changeCard(deltaY < 0 ? 1 : -1); + if (Math.abs(deltaX) < 48 || Math.abs(deltaX) <= Math.abs(deltaY) * 1.2) return; + changeCard(deltaX < 0 ? 1 : -1); }; if (!card) return
No Discover cards are available.
; return (
{ - if (event.key === 'ArrowUp') { + if (event.key === 'ArrowRight') { event.preventDefault(); changeCard(1); - } else if (event.key === 'ArrowDown') { + } else if (event.key === 'ArrowLeft') { event.preventDefault(); changeCard(-1); } }} onPointerDown={(event) => { - pointerStart.current = scrollable ? null : { x: event.clientX, y: event.clientY }; + pointerStart.current = { x: event.clientX, y: event.clientY }; }} onPointerCancel={() => { pointerStart.current = null; @@ -727,22 +684,10 @@ function DiscoverView({

{card.insight}

- {index + 1} / {deck.length}
- - + +
diff --git a/mobile/src/ui/explore/__tests__/ExploreScreen.test.tsx b/mobile/src/ui/explore/__tests__/ExploreScreen.test.tsx index 74b2a2c..5a0b7f5 100644 --- a/mobile/src/ui/explore/__tests__/ExploreScreen.test.tsx +++ b/mobile/src/ui/explore/__tests__/ExploreScreen.test.tsx @@ -264,7 +264,6 @@ describe('Explore navigation and Discover deck', () => { expect(screen.getByRole('heading', { name: 'What do you feel like?' })).toBeInTheDocument(); expect(screen.queryByText(/Pick something useful/)).not.toBeInTheDocument(); - expect(screen.queryByLabelText(/saved Discover/i)).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Open sessions' })); expect(onOpenSessions).toHaveBeenCalledOnce(); }); @@ -318,35 +317,44 @@ describe('Explore navigation and Discover deck', () => { expect(document.querySelector('.discover-deck-card h1')?.textContent).not.toBe(firstTitle); expect(JSON.parse(localStorage.getItem('weft.explore.v1') ?? '{}').discoverIndex).toBe(1); - fireEvent.keyDown(deck, { key: 'ArrowDown' }); + fireEvent.keyDown(deck, { key: 'ArrowLeft' }); expect(document.querySelector('.discover-deck-card h1')?.textContent).toBe(firstTitle); }); - it('responds only to dominant vertical swipes and keeps Save local', () => { + it('responds only to dominant horizontal swipes', () => { renderExplore(); fireEvent.click(screen.getByRole('button', { name: 'Discover: Read something worth knowing' })); const deck = screen.getByRole('region', { name: 'Discover card deck' }); const firstTitle = document.querySelector('.discover-deck-card h1')?.textContent; fireEvent.pointerDown(deck, { clientX: 10, clientY: 100 }); - fireEvent.pointerUp(deck, { clientX: 100, clientY: 80 }); + fireEvent.pointerUp(deck, { clientX: 30, clientY: 20 }); expect(document.querySelector('.discover-deck-card h1')?.textContent).toBe(firstTitle); - fireEvent.pointerDown(deck, { clientX: 10, clientY: 100 }); - fireEvent.pointerUp(deck, { clientX: 12, clientY: 20 }); + fireEvent.pointerDown(deck, { clientX: 100, clientY: 100 }); + fireEvent.pointerUp(deck, { clientX: 20, clientY: 98 }); expect(document.querySelector('.discover-deck-card h1')?.textContent).not.toBe(firstTitle); - - fireEvent.click(screen.getByRole('button', { name: /^Save / })); - expect(localStorage.getItem('weft.explore.v1')).toContain('savedCardIds'); expect(screen.queryByRole('group', { name: 'Discover topics' })).not.toBeInTheDocument(); }); + it('removes the legacy per-card preference from Explore storage', async () => { + const legacyKey = 'savedCardIds'; + localStorage.setItem('weft.explore.v1', JSON.stringify({ + [legacyKey]: ['legacy-card'], + lastCategory: 'discover', + })); + const user = userEvent.setup(); + renderExplore(); + await user.click(screen.getByRole('button', { name: /discover/i })); + expect(localStorage.getItem('weft.explore.v1')).not.toContain(legacyKey); + expect(document.querySelectorAll('.discover-deck-actions button')).toHaveLength(2); + }); + it('starts a new shuffled cycle only after the current deck is exhausted', () => { const today = new Date(); const day = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; localStorage.setItem('weft.explore.v1', JSON.stringify({ lastCategory: 'discover', - savedCardIds: [], completedCardIds: [], discoverDay: day, discoverCycle: 2, diff --git a/mobile/src/ui/styles/explore.css b/mobile/src/ui/styles/explore.css index 217d942..ed7a032 100644 --- a/mobile/src/ui/styles/explore.css +++ b/mobile/src/ui/styles/explore.css @@ -85,23 +85,6 @@ white-space: nowrap; } -.explore-saved-count { - position: absolute; - top: 1px; - right: 0; - display: grid; - min-width: 16px; - height: 16px; - padding: 0 4px; - place-items: center; - border: 2px solid var(--bg); - border-radius: 999px; - color: #fff; - background: var(--accent); - font-size: 9px; - font-weight: 800; -} - .agent-pulse { display: flex; position: relative; @@ -561,23 +544,6 @@ font-weight: 800; } -.discover-save { - position: absolute; - top: 10px; - right: 9px; - display: grid; - width: 38px; - height: 38px; - place-items: center; - border: 0; - border-radius: 11px; - color: var(--fg-faint); - background: transparent; -} - -.discover-save svg { width: 18px; height: 18px; } -.discover-save:hover { color: var(--accent); background: var(--accent-soft); } - .discover-reader { max-width: 680px; margin: 0 auto; @@ -1270,10 +1236,6 @@ padding: 10px 12px; overflow-y: auto; outline: none; - touch-action: pan-x; -} - -.discover-view-scrollable { touch-action: pan-y; } @@ -1371,16 +1333,6 @@ font: 700 10px var(--mono); } -.discover-deck-actions .discover-save { - position: static; - display: inline-flex; - width: auto; - height: 38px; - padding: 0 9px; - align-items: center; - gap: 5px; -} - .discover-deck-actions > div { display: flex; gap: 5px; diff --git a/openspec/changes/add-explore-hub/design.md b/openspec/changes/add-explore-hub/design.md index 441e8d1..694910f 100644 --- a/openspec/changes/add-explore-hub/design.md +++ b/openspec/changes/add-explore-hub/design.md @@ -59,7 +59,7 @@ The pulse row always has a fixed layout height. Tapping a blocked state opens th ### Bundled content uses typed local data -The 50 Discover cards will be stored as typed application data with stable identifiers, category, title, summary, estimated duration, body sections, and optional illustration metadata. Reading and saved state will be persisted locally through the existing settings/storage conventions. +The 50 Discover cards will be stored as typed application data with stable identifiers, category, title, summary, estimated duration, body sections, and optional illustration metadata. Reading progress will be persisted locally through the existing settings/storage conventions. The initial cards use original text and local assets so Discover works offline and has no licensing or network dependency. @@ -88,7 +88,6 @@ Approvals and elicitations render in a Weft-owned overlay above every Explore ca Explore stores only: - last selected category, -- saved Discover card IDs, - per-card reading completion, - local game progress and daily scores, - unwind preferences such as vibration, diff --git a/openspec/changes/add-explore-hub/proposal.md b/openspec/changes/add-explore-hub/proposal.md index 9810d02..c5712c3 100644 --- a/openspec/changes/add-explore-hub/proposal.md +++ b/openspec/changes/add-explore-hub/proposal.md @@ -7,7 +7,7 @@ Long-running agent turns leave mobile users with little to do beyond watching a - Add a permanent compass action to the mobile session header without changing the existing read-only Live, Quiet, or Working status line. - Add a full-screen Explore destination with a compact always-visible Agent Pulse and four entry points: Discover, Watch, Play, and Unwind. - Add an Agent activity view derived from existing encrypted session state, including current intent, elapsed work, tool progress, completion, approval, and connection states. -- Add 50 original bundled Discover cards with local reading and save state. +- Add 50 original bundled Discover cards with local reading progress. - Add a lazy, isolated third-party short-video widget surface for Watch, with explicit loading, no session-context sharing, and a safe unavailable/configuration fallback. - Add two offline games: Threadline and Signal Steps. - Add two native rest experiences: Weave Breath and Eye Horizon. diff --git a/openspec/changes/add-explore-hub/specs/mobile-explore-hub/spec.md b/openspec/changes/add-explore-hub/specs/mobile-explore-hub/spec.md index 3ac1e4b..1a13874 100644 --- a/openspec/changes/add-explore-hub/specs/mobile-explore-hub/spec.md +++ b/openspec/changes/add-explore-hub/specs/mobile-explore-hub/spec.md @@ -52,10 +52,6 @@ Discover SHALL provide 50 original, bundled, offline-readable cards with stable - **WHEN** the device has no network connection - **THEN** the user can browse and read every bundled Discover card -#### Scenario: Save a card -- **WHEN** the user saves or unsaves a Discover card -- **THEN** the selection persists locally across application restarts - #### Scenario: Read a card - **WHEN** the user opens a Discover card - **THEN** the application presents readable typography, semantic headings, estimated duration, and a route back to the prior card position @@ -116,7 +112,7 @@ Explore SHALL preserve Weft-owned approvals, elicitations, errors, and completed Explore SHALL keep personalization and activity state local and SHALL NOT disclose private Weft session context to content providers. #### Scenario: Persist local preferences -- **WHEN** the user changes category, saved cards, game progress, or unwind preferences +- **WHEN** the user changes category, reading progress, game progress, or unwind preferences - **THEN** the application stores only those Explore-specific values in local application storage #### Scenario: Load external content diff --git a/openspec/changes/add-explore-hub/tasks.md b/openspec/changes/add-explore-hub/tasks.md index f9d02e9..09ab06f 100644 --- a/openspec/changes/add-explore-hub/tasks.md +++ b/openspec/changes/add-explore-hub/tasks.md @@ -17,8 +17,8 @@ - [x] 3.1 Define the typed Discover card model and add all 50 original bundled starter cards - [x] 3.2 Build the Discover card feed and accessible reader with estimated duration and return-position preservation -- [x] 3.3 Add local saved-card and reading-completion persistence using repository-standard storage helpers -- [x] 3.4 Add tests for catalog integrity, offline rendering, reader navigation, and saved state +- [x] 3.3 Add local reading-completion persistence using repository-standard storage helpers +- [x] 3.4 Add tests for catalog integrity, offline rendering, reader navigation, and reading state ## 4. Watch Integration diff --git a/openspec/changes/refine-explore-live-experience/design.md b/openspec/changes/refine-explore-live-experience/design.md index 1a1de7a..dab0b0c 100644 --- a/openspec/changes/refine-explore-live-experience/design.md +++ b/openspec/changes/refine-explore-live-experience/design.md @@ -1,6 +1,6 @@ ## Context -Explore 0.2.24 is a sibling application view layered above a still-mounted `SessionScreen`. It currently owns a Back button, saved-card action, top Agent Pulse, optional Agent Activity screen, Weft-owned interruption overlay, Discover filters/feed/reader, and category-specific activities. The active `SessionView` already exposes streaming assistant items assembled from `assistant_delta`, live intent, running tools, busy timing, approvals, elicitations, unread state, errors, and session status. +Explore 0.2.24 is a sibling application view layered above a still-mounted `SessionScreen`. It currently owns a Back button, top Agent Pulse, optional Agent Activity screen, Weft-owned interruption overlay, Discover filters/feed/reader, and category-specific activities. The active `SessionView` already exposes streaming assistant items assembled from `assistant_delta`, live intent, running tools, busy timing, approvals, elicitations, unread state, errors, and session status. The redesigned surface must fit normal phone viewports without page scrolling, preserve chat state, reuse the established session drawer, avoid a second activity/details destination, and remain accessible with touch, keyboard, screen readers, reduced motion, and enlarged text. @@ -12,14 +12,14 @@ The redesigned surface must fit normal phone viewports without page scrolling, p - Stream current assistant text in place and compact all fallback activity states into the same region. - Return directly to chat when the dock is activated. - Give Explore the same hamburger/session-drawer affordance as the active chat. -- Make Discover a one-card-at-a-time, vertically swipeable, balanced deck with no filters, list, or reader transition. +- Make Discover a one-card-at-a-time, horizontally swipeable, balanced deck with no filters, list, reader transition, or card-side secondary action. - Fit the Explore home and normal category experiences inside `100dvh` without page scrolling. -- Preserve local reading, saving, game, and unwind state through the existing bounded storage key. +- Preserve local reading, game, and unwind state through the existing bounded storage key. **Non-Goals:** - Supporting non-GitHub-Copilot agents or a generic agent protocol. -- Building recommendations, accounts, cloud personalization, or a saved-card library. +- Building recommendations, accounts, cloud personalization, or a content library. - Adding new protocol messages; all live dock data comes from the existing decrypted session projection. - Turning Explore into a mobile IDE, file browser, or diff viewer in this change. - Guaranteeing zero scrolling at extreme accessibility text scales where clipping would be worse. @@ -34,7 +34,7 @@ Alternative considered: duplicate the drawer or lift all drawer state and render ### The header is a stable shared-navigation header -Explore will show the existing unread-aware hamburger on mobile, the existing static Weft mark on desktop-wide layouts, a compass mark, and the single title `Explore`. It will have no Back action, subtitle, saved-card action, count, or right-side control. +Explore will show the existing unread-aware hamburger on mobile, the existing static Weft mark on desktop-wide layouts, a compass mark, and the single title `Explore`. It will have no Back action, subtitle, count, or right-side control. System/browser Back remains hierarchical: category or activity detail returns to Explore home, then the next Back returns to chat. Internal games and unwind activities retain small in-content return controls. @@ -56,9 +56,9 @@ Alternative considered: keep Agent Pulse plus an Agent Activity destination. Rej ### Discover is one complete card, not a feed plus reader -Discover will render one card that includes topic, duration, title, summary, useful insight, optional illustration, Save, and deck progress. The existing repeated explanatory paragraph is removed. Topic filters, feed cards, saved-list header entry, and reader state are removed. +Discover will render one card that includes topic, duration, title, summary, useful insight, optional illustration, and deck progress. The existing repeated explanatory paragraph is removed. Topic filters, feed cards, and reader state are removed. -Vertical swipe up advances and swipe down returns to the previous card. Arrow keys and explicit accessible Previous/Next controls provide equivalent operation. Horizontal gestures remain unused to avoid conflict with platform Back behavior. +Horizontal swipe left advances and swipe right returns to the previous card. Arrow Left/Right and explicit accessible Previous/Next controls provide equivalent operation. Vertical gestures remain available for accessibility overflow scrolling. ### The deck is balanced, deterministic, and locally resumable @@ -72,7 +72,7 @@ The deck builder will: - persist seed/day and current index, - advance to a new seeded cycle only after exhaustion. -Saving remains a local signal for future personalization but does not expose a saved-card view. The first release does not alter deck order using saved or completed state. +Completed-card state records cycle progress without changing deck order. Alternative considered: `Array.sort(() => Math.random() - 0.5)` on each mount. Rejected because it is biased, unstable across renders, clusters topics, and cannot restore position. @@ -90,20 +90,19 @@ Entering a category or activity detail creates at most one Explore-detail histor - **Streaming text changes too rapidly to read** → Normalize whitespace, show only the newest bounded excerpt, and update in place without marquee motion. - **A long card overflows short phones** → Enforce content limits, remove decorative media at short heights, and allow accessibility-only internal scrolling. -- **Vertical swipe conflicts with card scrolling** → Standard cards do not scroll; accessibility overflow mode disables swipe capture while content is actively scrolling. +- **Horizontal swipe conflicts with platform navigation** → Discover requires a dominant horizontal threshold and retains explicit controls, while vertical gestures remain native scrolling. - **Drawer integration destabilizes SessionScreen** → Reuse its existing state and component, keep Explore as an overlay within the same mounted shell, and add App/session integration tests. - **Daily order feels repetitive** → Persist a complete cycle, rotate by day, and reseed after catalog exhaustion without repeating within a cycle. - **Removing Explore approval overlays delays action** → The dock uses a high-contrast attention state and returns directly to the canonical chat card. -- **Removing the saved library makes Save feel incomplete** → Treat Save explicitly as a lightweight “remember this” signal; do not advertise browsing saved content until that product is designed. ## Migration Plan 1. Extend the existing Explore storage record with optional deck day, seed, and index fields; tolerate all 0.2.24 records. -2. Keep existing saved and completed card IDs. +2. Keep existing completed card IDs and discard the obsolete per-card preference field. 3. Remove obsolete topic/reader/activity-detail UI without deleting compatible stored fields. 4. Ship through the standard versioned `ship.ps1` production pipeline. 5. Roll back by restoring the 0.2.24 Explore module; additive storage fields remain harmless. ## Open Questions -None. The agreed interaction is fixed: shared hamburger header, bottom live dock returning to chat, and balanced vertical Discover cards with no list or filters. +None. The agreed interaction is fixed: shared hamburger header, bottom live dock returning to chat, and balanced horizontal Discover cards with no list or filters. diff --git a/openspec/changes/refine-explore-live-experience/proposal.md b/openspec/changes/refine-explore-live-experience/proposal.md index 66cf185..4404db3 100644 --- a/openspec/changes/refine-explore-live-experience/proposal.md +++ b/openspec/changes/refine-explore-live-experience/proposal.md @@ -7,11 +7,11 @@ Explore currently behaves like a nested content page: Agent Pulse occupies premi - Replace the top Agent Pulse and Agent Activity screen with a persistent bottom Live Copilot Dock. - Stream the latest assistant text in the dock, with intent, active tool, elapsed work, reply-ready, blocked, error, and idle fallbacks compacted into the same fixed space. - Make the dock return directly to the active chat when activated. -- Replace the Explore Back and saved-card header actions with the existing sessions hamburger, compass mark, and a compact Explore title. +- Replace the Explore Back and right-side header actions with the existing sessions hamburger, compass mark, and a compact Explore title. - Reuse the existing session drawer rather than introducing an Explore-specific menu. -- Replace Discover topic filters, multi-card feed, and separate reader with one complete vertically swipeable card at a time. +- Replace Discover topic filters, multi-card feed, and separate reader with one complete horizontally swipeable card at a time. - Build a deterministic balanced deck that varies topics, avoids repeats until exhaustion, and preserves position locally. -- Keep per-card Save as a local signal for later personalization without exposing a saved-content list or personalization controls. +- Remove the Discover card-side secondary action and its persisted field. - Reflow Explore home, Watch, Play, and Unwind into a fixed `100dvh` shell with no page scrolling at standard mobile text sizes. - Preserve safe internal scrolling only when accessibility text scaling makes it necessary. diff --git a/openspec/changes/refine-explore-live-experience/specs/mobile-explore-live-experience/spec.md b/openspec/changes/refine-explore-live-experience/specs/mobile-explore-live-experience/spec.md index ce69403..8f878ad 100644 --- a/openspec/changes/refine-explore-live-experience/specs/mobile-explore-live-experience/spec.md +++ b/openspec/changes/refine-explore-live-experience/specs/mobile-explore-live-experience/spec.md @@ -9,7 +9,7 @@ Explore SHALL use the established Weft sessions navigation affordance rather tha #### Scenario: Compact header - **WHEN** Explore is displayed on mobile -- **THEN** the header contains the sessions button, compass mark, and Explore title without a Back button, subtitle, saved-card icon, count, or right-side action +- **THEN** the header contains the sessions button, compass mark, and Explore title without a Back button, subtitle, count, or right-side action #### Scenario: Desktop-wide navigation - **WHEN** the persistent desktop session sidebar is visible @@ -47,23 +47,23 @@ Discover SHALL present one complete useful-idea card at a time without topic fil #### Scenario: Open Discover - **WHEN** the user selects Discover -- **THEN** the application immediately shows one complete card with topic, duration, title, summary, useful insight, Save action, and deck position +- **THEN** the application immediately shows one complete card with topic, duration, title, summary, useful insight, and deck position #### Scenario: Advance by touch -- **WHEN** the user swipes upward beyond the gesture threshold +- **WHEN** the user swipes left beyond the gesture threshold - **THEN** Discover advances exactly one card #### Scenario: Return by touch -- **WHEN** the user swipes downward beyond the gesture threshold and a prior card exists +- **WHEN** the user swipes right beyond the gesture threshold and a prior card exists - **THEN** Discover returns exactly one card #### Scenario: Operate without touch -- **WHEN** the user presses Arrow Up, Arrow Down, or the accessible Previous and Next controls +- **WHEN** the user presses Arrow Left, Arrow Right, or the accessible Previous and Next controls - **THEN** Discover performs the equivalent single-card navigation -#### Scenario: Avoid platform gesture conflict -- **WHEN** the user makes a predominantly horizontal gesture -- **THEN** Discover does not change cards or suppress platform Back navigation +#### Scenario: Preserve vertical scrolling +- **WHEN** enlarged content requires scrolling +- **THEN** vertical gestures scroll the current card without changing cards ### Requirement: Balanced deterministic discovery Discover SHALL generate a locally deterministic deck that varies topics and avoids repeats until the catalog is exhausted. @@ -82,11 +82,7 @@ Discover SHALL generate a locally deterministic deck that varies topics and avoi #### Scenario: Exhaust a cycle - **WHEN** the user reaches the final unseen card -- **THEN** the next advance begins a newly seeded complete cycle without losing saved-card state - -#### Scenario: Save without filtering -- **WHEN** the user saves or unsaves the current card -- **THEN** the application persists that local signal without opening or exposing a saved-card list +- **THEN** the next advance begins a newly seeded complete cycle ### Requirement: Single-screen Explore layout Explore SHALL fit its standard mobile experiences within the dynamic viewport without page scrolling. diff --git a/openspec/changes/refine-explore-live-experience/tasks.md b/openspec/changes/refine-explore-live-experience/tasks.md index a6bf333..0eecce6 100644 --- a/openspec/changes/refine-explore-live-experience/tasks.md +++ b/openspec/changes/refine-explore-live-experience/tasks.md @@ -1,7 +1,7 @@ ## 1. Shared Explore Shell - [x] 1.1 Render Explore within the mounted active session shell so it can reuse the existing session drawer -- [x] 1.2 Replace the Explore Back and saved-card header actions with the mobile hamburger or desktop Weft mark, compass, and compact title +- [x] 1.2 Replace the Explore Back and right-side header actions with the mobile hamburger or desktop Weft mark, compass, and compact title - [x] 1.3 Preserve system Back behavior from category or activity to Explore home and then chat - [x] 1.4 Reflow the Explore shell into fixed header, optional tabs, flexible content, and bottom dock viewport bands - [x] 1.5 Compact Explore home to a no-scroll title and `2 x 2` category grid without the introductory paragraph or Continue card @@ -20,11 +20,11 @@ - [x] 3.1 Replace topic filters, feed cards, and reader state with one complete Discover card - [x] 3.2 Implement deterministic per-topic shuffling and balanced topic interleaving from a daily seed - [x] 3.3 Persist deck day, cycle seed, and current index while migrating existing Explore storage safely -- [x] 3.4 Implement vertical swipe thresholds for next and previous without intercepting horizontal gestures -- [x] 3.5 Add Arrow Up, Arrow Down, and accessible Previous and Next controls -- [x] 3.6 Keep per-card Save as local state without a saved-card list, filter, icon, or count +- [x] 3.4 Implement horizontal swipe thresholds for next and previous while preserving vertical scrolling +- [x] 3.5 Add Arrow Left, Arrow Right, and accessible Previous and Next controls +- [x] 3.6 Remove the per-card secondary action and its legacy persisted field - [x] 3.7 Add catalog length constraints and remove repeated reader-only copy so every standard card fits -- [x] 3.8 Add logic and interaction tests for deterministic balance, no repeats, persistence, gestures, keyboard operation, cycle exhaustion, and saving +- [x] 3.8 Add logic and interaction tests for deterministic balance, no repeats, persistence, gestures, keyboard operation, cycle exhaustion, and storage migration ## 4. Category Viewport Fitting