Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/core/guide-viewer-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import type { GuideViewerAssets } from "./guide-format";

export const GUIDE_VIEWER_MANIFEST: Omit<GuideViewerAssets, "baseUrl"> = {
js: "viewer.CTfggrYt.js",
css: "viewer.BdruF6Mj.css",
jsIntegrity: "sha384-It85Hkx0/d1Xme4SJjt3shHybLPGucRF/OODzE84mbOmGH8fK66eFNzgFRXe2W4Z",
cssIntegrity: "sha384-9i0z0HV8a5Hr0SAQt0+pUfQE96MTbGaCWtZlSzhk+HKIHXsqrGi16HQA4mlEWRvx",
js: "viewer.DsVT7zHg.js",
css: "viewer.BZrAP09o.css",
jsIntegrity: "sha384-GqsJAyczOLBwgFdmWfMxhi3P95GhDLgzDfgQOvoX7A7SH/25lxgU3wTUQMmnzzvd",
cssIntegrity: "sha384-qqI5FLKY4BfdFBlERBab5UhQOeP12/PBvKklCqJX+1P3WrMnSA1rORxnYpdqHBRp",
langs: {
"astro": "chunks/astro.BykyiR6i.js",
"c": "chunks/c.BIGW1oBm.js",
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5716,6 +5716,7 @@ const App: React.FC = () => {
isApiMode={isApiMode && !documentReadOnly}
initialTab={initialExportTab}
wrapCopiedAnnotations={wrapCopiedFeedback}
restoreFocusId={isCompactTouchLayout ? 'pn-compact-plan-options-trigger' : 'pn-plan-options-trigger'}
/>

{/* Import Modal */}
Expand All @@ -5724,6 +5725,7 @@ const App: React.FC = () => {
onClose={() => setShowImport(false)}
onImport={importFromShareUrl}
shareBaseUrl={shareBaseUrl}
restoreFocusId={isCompactTouchLayout ? 'pn-compact-plan-options-trigger' : 'pn-plan-options-trigger'}
/>

{/* Feedback prompt dialog */}
Expand Down
6 changes: 4 additions & 2 deletions packages/review-editor/components/AIConfigBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type React from 'react';
import { useState, useEffect, useRef } from 'react';
import { getProviderMeta } from '@plannotator/ui/components/ProviderIcons';
import { type AIProviderOption } from '@plannotator/ui/utils/aiProvider';
import { shouldAutoFocusPassiveSearch } from '@plannotator/ui/hooks/useViewportEnvironment';

interface AIConfigBarProps {
providers: AIProviderOption[];
Expand Down Expand Up @@ -162,16 +163,17 @@ export const AIConfigBar: React.FC<AIConfigBarProps> = ({
</button>

{openMenu === 'model' && (
<div className="ai-config-menu">
<div data-pn-secondary-input-picker className="ai-config-menu">
{models.length > 8 && (
<div className="ai-config-menu-search">
<input
data-pn-mobile-editable
ref={searchInputRef}
type="text"
placeholder="Filter models…"
value={modelSearch}
onChange={e => setModelSearch(e.target.value)}
autoFocus
autoFocus={shouldAutoFocusPassiveSearch()}
/>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/review-editor/components/AITab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ const GeneralInput: React.FC<{
<div className="border-t border-border/50 p-2">
<div className="flex items-end gap-1.5">
<textarea
data-pn-mobile-editable
ref={textareaRef}
value={value}
onChange={(e) => onChange(e.target.value)}
Expand Down
2 changes: 2 additions & 0 deletions packages/review-editor/components/AnnotationToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
<div className="flex items-center justify-between mb-1">
<span className="text-[10px] text-muted-foreground">Suggested code</span>
<button
id="pn-suggestion-editor-trigger"
onClick={() => setShowCodeModal(true)}
className="p-0.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
title="Expand editor"
Expand All @@ -243,6 +244,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
</button>
</div>
<textarea
data-pn-mobile-editable
ref={suggestedCodeRef}
value={suggestedCode}
onChange={(e) => setSuggestedCode(e.target.value)}
Expand Down
1 change: 1 addition & 0 deletions packages/review-editor/components/AskAIInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const AskAIInput: React.FC<AskAIInputProps> = ({
</div>

<textarea
data-pn-mobile-editable
value={question}
onChange={(e) => setQuestion(e.target.value)}
placeholder="Ask about this code..."
Expand Down
7 changes: 5 additions & 2 deletions packages/review-editor/components/BaseBranchPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Popover } from '@base-ui/react/popover';
import type { AvailableBranches, CompareTargetPickerCopy, RecentCommit } from '@plannotator/shared/types';
import { shouldAutoFocusPassiveSearch } from '@plannotator/ui/hooks/useViewportEnvironment';

interface BaseBranchPickerProps {
availableBranches: AvailableBranches;
Expand Down Expand Up @@ -183,11 +184,13 @@ export const BaseBranchPicker: React.FC<BaseBranchPickerProps> = ({
<Popover.Portal>
<Popover.Positioner side="bottom" align="start" sideOffset={4} className="z-50">
<Popover.Popup
className="w-80 bg-popover text-popover-foreground border border-border rounded shadow-lg overflow-hidden origin-[var(--transform-origin)] transition-opacity data-starting-style:opacity-0 data-ending-style:opacity-0"
initialFocus={() => searchRef.current}
data-pn-secondary-input-picker
className="w-80 max-w-[calc(100vw-2rem)] bg-popover text-popover-foreground border border-border rounded shadow-lg overflow-hidden origin-[var(--transform-origin)] transition-opacity data-starting-style:opacity-0 data-ending-style:opacity-0"
initialFocus={shouldAutoFocusPassiveSearch() ? () => searchRef.current : false}
>
<div className="p-2 border-b border-border/50">
<input
data-pn-mobile-editable
ref={searchRef}
type="text"
value={query}
Expand Down
7 changes: 7 additions & 0 deletions packages/review-editor/components/CallFlowSearchControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function CallFlowSearchControls({
<Search aria-hidden="true" size={13} strokeWidth={1.75} />
<span className="sr-only">{label}</span>
<input
data-pn-mobile-editable
ref={inputRef}
type="search"
value={query}
Expand All @@ -59,6 +60,8 @@ export function CallFlowSearchControls({
{matchCount === 0 ? '0/0' : `${Math.min(currentMatchIndex + 1, matchCount)}/${matchCount}`}
</span>
<button
data-pn-touch-target
data-pn-touch-target-icon
type="button"
className="call-flow-icon-button"
onClick={() => onMoveMatch(-1)}
Expand All @@ -69,6 +72,8 @@ export function CallFlowSearchControls({
<ChevronUp aria-hidden="true" size={14} />
</button>
<button
data-pn-touch-target
data-pn-touch-target-icon
type="button"
className="call-flow-icon-button"
onClick={() => onMoveMatch(1)}
Expand All @@ -79,6 +84,8 @@ export function CallFlowSearchControls({
<ChevronDown aria-hidden="true" size={14} />
</button>
<button
data-pn-touch-target
data-pn-touch-target-icon
type="button"
className="call-flow-icon-button"
onClick={onClose}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('ExpandedCommentDialog mobile composition', () => {
const suggestButton = Array.from(document.querySelectorAll('button'))
.find(button => button.textContent?.trim() === 'Suggest code');
expect(suggestButton).not.toBeNull();
expect(suggestButton?.id).toBe('pn-suggestion-editor-trigger');
await act(async () => suggestButton?.click());
expect(suggestionOpens).toBe(1);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const ExpandedCommentDialog: React.FC<ExpandedCommentDialogProps> = ({
)}
{onEditSuggestion && (
<button
id="pn-suggestion-editor-trigger"
type="button"
onClick={onEditSuggestion}
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
Expand Down
1 change: 1 addition & 0 deletions packages/review-editor/components/FileCommentBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const FileCommentCard: React.FC<{
{isEditing ? (
<div className="mt-1" onClick={(e) => e.stopPropagation()}>
<textarea
data-pn-mobile-editable
autoFocus
value={draft}
onChange={(e) => setDraft(e.target.value)}
Expand Down
1 change: 1 addition & 0 deletions packages/review-editor/components/PRCommentsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ export const PRCommentsTab: React.FC<PRCommentsTabProps> = React.memo(({ context
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
data-pn-mobile-editable
ref={searchInputRef}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
Expand Down
3 changes: 3 additions & 0 deletions packages/review-editor/components/PanelChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ export function PanelSearchField({
/>
</svg>
<input
data-pn-mobile-editable
ref={inputRef}
type="text"
value={query}
Expand All @@ -399,6 +400,8 @@ export function PanelSearchField({
</span>
)}
<button
data-pn-touch-target
data-pn-touch-target-icon
type="button"
onClick={hasQuery ? onClear : onClose}
className="rounded p-0.5 text-muted-foreground transition-colors hover:bg-background/50 hover:text-foreground"
Expand Down
83 changes: 83 additions & 0 deletions packages/review-editor/components/SuggestionModal.mobile.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { afterEach, describe, expect, test } from 'bun:test';
import React, { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { SuggestionModal } from './SuggestionModal';

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('SuggestionModal mobile containment', () => {
test.skipIf(!hasDom)('uses the visible viewport and compact-safe editor structure', async () => {
host = document.createElement('div');
document.body.appendChild(host);
root = createRoot(host);

await act(async () => {
root?.render(
<SuggestionModal
filePath="src/example.ts"
toolbarState={null}
selectedOriginalCode="const value = 1;"
suggestedCode="const value = 2;"
setSuggestedCode={() => {}}
modalLayout="horizontal"
setModalLayout={() => {}}
onClose={() => {}}
/>,
);
});

const stage = document.querySelector<HTMLElement>('.pn-visible-viewport-overlay');
const dialog = document.querySelector<HTMLElement>('[role="dialog"]');
const panes = document.querySelector<HTMLElement>('.pn-suggestion-panes');
const editor = document.querySelector<HTMLTextAreaElement>('textarea');
const labelledButtons = document.querySelectorAll<HTMLButtonElement>('button[aria-label]');

expect(stage).not.toBeNull();
expect(dialog?.getAttribute('aria-modal')).toBe('true');
expect(dialog?.hasAttribute('aria-labelledby')).toBe(true);
expect(dialog?.hasAttribute('data-pn-secondary-input-dialog')).toBe(true);
expect(panes?.classList.contains('min-h-0')).toBe(true);
expect(editor?.getAttribute('data-pn-mobile-editable')).toBe('true');
expect(editor?.className).toContain('min-h-[300px]');
expect(labelledButtons.length).toBe(2);
});

test.skipIf(!hasDom)('delegates Escape dismissal to the dialog primitive', async () => {
let closeCount = 0;
host = document.createElement('div');
document.body.appendChild(host);
root = createRoot(host);

await act(async () => {
root?.render(
<SuggestionModal
filePath="src/example.ts"
toolbarState={null}
selectedOriginalCode="const value = 1;"
suggestedCode="const value = 2;"
setSuggestedCode={() => {}}
modalLayout="horizontal"
setModalLayout={() => {}}
onClose={() => { closeCount += 1; }}
/>,
);
});

await act(async () => {
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
});

expect(closeCount).toBe(1);
});
});
Loading