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;
```