mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
8e88dcec8c
* fix(plan): make the compact TOC scroll the document again The compact navigator overlay rendered outside App's ScrollViewportProvider, so the TableOfContents it hosts resolved a null viewport and every "jump to heading" tap was a silent no-op on phones. The provider is context-only, so hoisting it above the overlay fixes the lookup without touching desktop DOM structure or order. * fix(plan): scope the permission-mode chooser to plan review and bound its card The one-time chooser fired in every non-goal-setup Claude Code session, so annotate, annotate-last, annotate-folder and archive reviewers got a blocking dialog about what happens after plan approval. Gate it on plan review, which is the absence of a mode field in the /api/plan payload. The card itself was hand-rolled with no height cap and no internal scroll, so on a short landscape phone it overflowed both edges of a modal that has no dismiss control. Give it the same bounded shell the sibling one-time dialogs use: safe-area padding, a visible-viewport max height, and the option list as the only scrolling region. Content and cookie behavior are unchanged. * fix(review): never seed Tree over a persisted panel view The first-run initializer gated only on the setup-seen cookie, but sessions that never reach it (non-git, workspace, PR, no since-base) still let Settings persist a panel view. A reviewer could hold an explicit Git status choice with "seen" unset, and the next plain git session seeded Tree over it. Treat a persisted view as the decision: consume the one-time setup and write nothing. * fix(comments): give the geometry-forced composer a working Escape When the anchor has no room the position tracker forces dialog mode. On a fine-pointer viewport Escape took the collapse branch, the tracker instantly re-forced the dialog, and the keystroke was eaten; the Collapse button bounced the same way. Track forced expansion separately from the preferred kind: in that state Escape closes (draft-preserving) and Collapse is hidden, because collapsing is geometrically impossible. * fix(review): stop the compact Editor tab editing the desktop diff style The dock's Split/Unified control returns null under the compact touch layout, but the Settings copy of it kept rendering while the phone showed the session-only unified diff. It looked dead and silently rewrote the persisted desktop preference. Hide it on compact and state what the session is doing; the prop defaults to false, so the plan editor and desktop are untouched. * fix(portal): give the share portal the mobile app shell The portal mounts the same plan editor App as the hook but kept the pre-mobile entry document: no viewport-fit=cover (so every safe-area token was inert) and a min-h-screen body without the shell's scroll ownership. Mirror the hook's body class, root class, and viewport meta, and extend the entry-asset pin to cover the portal alongside them. * fix(plan): keep compact overlays out of the printed document The compact plan stage and the compact navigator are full-viewport transient surfaces with no print-hide marker, so printing on a touch device with Annotations, Ask AI, Versions or Archive open clipped the document behind them. Mark both with data-print-hide, which print.css already hides. The desktop rail is untouched. * fix(plan): give the selection toolbar real touch targets Copy / Delete / Comment / quick label / looks-good / Cancel measured 28x28 with 2px gaps on a phone because the toolbar never got the touch-target markers the rest of the stack uses. Stamp them on its buttons and add a compact-scoped gap so adjacent destructive and comment actions are not a mis-tap apart. Both are inert outside the compact scope, so desktop geometry is unchanged. * docs: keep the new QA-batch comments free of em dashes
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test';
|
|
import React, { act } from 'react';
|
|
import { createRoot, type Root } from 'react-dom/client';
|
|
import { PermissionModeSetup } from './PermissionModeSetup';
|
|
|
|
/**
|
|
* The chooser is modal and has no dismiss control, so a card taller than the
|
|
* viewport is a trap: on a short landscape phone its options and its Continue
|
|
* button both sat off-screen with nothing scrollable. Pin the bounded shell.
|
|
*/
|
|
|
|
const hasDom = typeof document !== 'undefined';
|
|
let root: Root | null = null;
|
|
let host: HTMLElement | null = null;
|
|
|
|
afterEach(async () => {
|
|
if (root) await act(async () => root?.unmount());
|
|
root = null;
|
|
host?.remove();
|
|
host = null;
|
|
if (hasDom) document.body.replaceChildren();
|
|
});
|
|
|
|
describe.if(hasDom)('PermissionModeSetup shell', () => {
|
|
test('caps the card to the visible viewport and scrolls its options', async () => {
|
|
host = document.createElement('div');
|
|
document.body.appendChild(host);
|
|
root = createRoot(host);
|
|
await act(async () => {
|
|
root?.render(<PermissionModeSetup isOpen onComplete={() => {}} />);
|
|
});
|
|
|
|
const card = document.querySelector<HTMLElement>('.bg-card.max-w-lg');
|
|
if (!card) throw new Error('permission mode card did not render');
|
|
expect(card.style.maxHeight).toContain('--pn-viewport-height');
|
|
expect(card.className).toContain('flex-col');
|
|
|
|
const scroller = Array.from(card.children).find(
|
|
(child) => child instanceof HTMLElement && child.className.includes('overflow-y-auto'),
|
|
);
|
|
expect(scroller).not.toBeUndefined();
|
|
|
|
// The confirm control stays inside the card, below the scrolling region,
|
|
// so it can never be pushed past the viewport edge.
|
|
const confirm = Array.from(card.querySelectorAll('button'))
|
|
.find((button) => button.textContent?.trim() === 'Continue');
|
|
expect(confirm).not.toBeUndefined();
|
|
});
|
|
});
|