diff --git a/docs/superpowers/plans/2026-08-30-notebook-story.md b/docs/superpowers/plans/2026-08-30-notebook-story.md index 180cd0d..67786ad 100644 --- a/docs/superpowers/plans/2026-08-30-notebook-story.md +++ b/docs/superpowers/plans/2026-08-30-notebook-story.md @@ -42,7 +42,7 @@ - Test: `app/components/story/NotebookStory.test.tsx` — modo reduzido, elegibilidade 3D e integração de tabs. - Modify: `app/components/cinematic/CinematicHome.tsx` — usar `NotebookStory` e remover `PrintReel`/`ScrollStory`. - Modify: `app/globals.css` — somente tokens/classes novas do notebook, sem tocar no `globals.css` raiz do usuário. -- Delete after migration: `app/components/story/PrintReel.tsx`, `app/components/story/ScrollStory.tsx`, `app/components/story/StoryStage3D.tsx`, `app/components/story/phoneScreenTexture.ts` e testes órfãos. +- Delete after migration: `app/components/story/PrintReel.tsx`, `app/components/story/ScrollStory.tsx`, `app/components/story/StoryStage3D.tsx`, `app/components/story/phoneScreenTexture.ts`, seus testes órfãos e `public/models/phone-quaternius.glb`. --- @@ -69,7 +69,7 @@ Invoke-WebRequest ` - [ ] **Step 2: Inspect the GLB node and material names before code** -Read the GLB header and JSON chunk with a one-off Node script. Record the actual node names, parent relationships, bounding boxes and material names in the implementation notes. The script must fail if there is no separate lid-like node. +Read the GLB header and JSON chunk with a one-off Node script. Record the actual validated mapping (`Laptop_Base`, `Laptop_Lid`), parent relationships, bounding boxes and material names in `NotebookStage3D.tsx` and `CREDITS.md`. The script must fail if there is no separate lid-like node. ```js const fs = require('node:fs'); @@ -172,7 +172,7 @@ const projectsFixture = [ }, { id: "sc-plus", - title: "SCPLUS", + title: "SC Plus Aquicultura", subtitle: "Landing Page Institucional", demoUrl: "https://scplus.evandro.dev.br", technologies: ["Next.js", "React"], @@ -344,14 +344,14 @@ export default function NotebookBrowser(props: NotebookBrowserProps): JSX.Elemen - [ ] **Step 1: Write failing semantic and interaction tests** -Test observable behavior, not implementation details: +Test observable behavior, not implementation details. Import `userEvent` in the test file before using `userEvent.setup()`: ```tsx it('renders the active project as an accessible tabpanel', () => { render(); expect(screen.getByRole('tablist', { name: /projetos em produção/i })).toBeInTheDocument(); expect(screen.getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true'); - expect(screen.getByRole('tabpanel')).toHaveAccessibleName(/SCPLUS/i); + expect(screen.getByRole('tabpanel')).toHaveAccessibleName(/SC Plus Aquicultura/i); }); it('moves between tabs with arrow keys and Home/End', async () => { @@ -510,21 +510,26 @@ export default function NotebookStage3D(props: NotebookStage3DProps): JSX.Elemen - [ ] **Step 1: Write failing stage boundary tests** -Mock only the R3F/Drei renderer boundary; assert the observable fallback contract. The test mock must expose the hook used by production: +Mock only the R3F/Drei renderer boundary; assert the observable fallback contract. Keep the `useGLTF` mock persistent for the failure case because React error boundaries may retry rendering: ```tsx jest.mock("@react-three/drei", () => ({ - ...jest.requireActual("@react-three/drei"), + Html: ({ children }: { children: React.ReactNode }) =>
{children}
, useGLTF: jest.fn(() => ({ scene: new THREE.Group() })), })); const mockedUseGLTF = jest.mocked(useGLTF); -function mockUseGLTFFailure() { - mockedUseGLTF.mockImplementationOnce(() => { +beforeEach(() => { + mockedUseGLTF.mockImplementation(() => ({ scene: new THREE.Group() })); +}); + +it("returns usable content when GLB loading fails", () => { + mockedUseGLTF.mockImplementation(() => { throw new Error("GLB failed"); }); -} + // render and assert the DOM fallback, not the Canvas internals +}); ``` - [ ] **Step 2: Run stage tests and verify the expected red failure** @@ -536,10 +541,8 @@ pnpm exec jest app/components/story/NotebookStage3D.test.tsx --runInBand - [ ] **Step 3: Implement the GLB rig** Implementation requirements: - - `useGLTF('/models/laptop-kenney.glb')` only; no remote URL. -- Clone the scene and apply materials from the notebook color map. -- Resolve the validated base/lid/screen/keyboard nodes from Task 1. +- Clone the scene, resolve the validated `Laptop_Base` and `Laptop_Lid` nodes, and apply materials from the notebook color map. - Attach the browser surface to a calibrated screen anchor on the lid using Drei `Html transform`. - Apply `getNotebookFrame(progress.current, projects.length)` to lid angle, screen opacity, device opacity and cursor props. - Keep base stable; only lid articulates. @@ -584,6 +587,8 @@ export type NotebookStoryProps = { projects: PortfolioProject[]; }; +export function canUseNotebook3D(view: Pick): boolean; +export function getNotebookSeekProgress(index: number, tabCount: number): number; export default function NotebookStory(props: NotebookStoryProps): JSX.Element; ```