Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4b499c0
fix(editor): restore continuous-page drawing and responsive navigation
markm39 Oct 19, 2026
575dc3f
fix(canvas): bound page sidebar work and audit native stalls
markm39 Oct 19, 2026
7027cbe
test(canvas): audit autosave drawing with prepared native loads
markm39 Oct 19, 2026
58d30f7
perf(ios): warm Metal before opening the first notebook
markm39 Oct 19, 2026
ef64dcc
fix(ci): align native audit toolchain and bound simulator boot
markm39 Oct 19, 2026
d34e7e4
perf(canvas): use prepared GPU rendering and verify actual gesture wi…
markm39 Oct 19, 2026
f972aa6
perf(canvas): use hardware pressure path tiles and profile missed bud…
markm39 Oct 19, 2026
c01f287
perf(canvas): avoid CPU live preview copies during input
markm39 Oct 19, 2026
be96d5d
perf(canvas): bound retained GPU tile resources during navigation
markm39 Oct 19, 2026
33a8395
fix(canvas): keep drawable acquisition off the input thread
markm39 Oct 21, 2026
df68018
perf(canvas): pin pipelined drawable rendering
markm39 Oct 21, 2026
d1b111e
test(canvas): separate animation idle from app idle
markm39 Oct 21, 2026
866a8ab
feat(theme): display dark paper while preserving authored exports
markm39 Oct 21, 2026
3620a2e
fix(theme): pin verified renderer and assert ink contrast in both app…
markm39 Oct 21, 2026
ba8bd97
ci(canvas): profile functional failures and preserve original fixture…
markm39 Oct 21, 2026
27b579f
feat(editor): integrate optimized canvas for TestFlight
markm39 Oct 30, 2026
6b7aca8
fix(release): use project build number in app bundle
markm39 Oct 30, 2026
e22e05b
chore(release): prepare TestFlight version 1.3.7
markm39 Oct 30, 2026
99950c8
feat(editor): refine ink and add notebook preferences
markm39 Oct 31, 2026
c57c17e
test(editor): exercise preferences and photo covers
markm39 Oct 31, 2026
644cbc4
fix(images): report photo access and copy failures
markm39 Oct 31, 2026
59ec86f
test(ios): retain zoom evidence after interaction failures
markm39 Oct 31, 2026
8e47927
fix(ui): expose editor and note actions to accessibility
markm39 Oct 31, 2026
c2faeac
test(ios): prepare photo access for cover picker
markm39 Oct 31, 2026
453ada5
test(ios): capture photo picker hierarchy
markm39 Oct 31, 2026
ab6f69c
perf(ios): reduce zoom rendering stalls
markm39 Oct 31, 2026
4295acc
fix(editor): draw above images and speed up crayon
markm39 Oct 31, 2026
4b664b1
test(editor): exercise drawing over images on iPad
markm39 Oct 31, 2026
bd0f393
feat(editor): preserve image and ink ordering
markm39 Oct 31, 2026
8213218
perf(editor): prepare dense eraser histories in background
markm39 Oct 31, 2026
1788b55
test(editor): stabilize dense eraser interaction
markm39 Oct 31, 2026
b1de25d
fix(editor): improve erased crayon rendering
markm39 Oct 31, 2026
a353b88
fix(editor): smooth selection drags and reset eraser cursor
markm39 Oct 31, 2026
576568a
fix(editor): select images by touch and update ink engine
markm39 Oct 31, 2026
a581e11
fix(library): regenerate missing note thumbnails
markm39 Nov 1, 2026
b7df6b0
build(ios): prepare build 31 for device testing
markm39 Nov 1, 2026
9d675d3
build(ios): prepare sharper zoom build 32
markm39 Nov 1, 2026
0b92a5b
revert(ios): restore smooth zoom for device testing
markm39 Nov 1, 2026
f4b68fb
chore(ios): remove superseded zoom test archive
markm39 Nov 1, 2026
ceaed95
build(ios): include GPU zoom preparation for device testing
markm39 Nov 1, 2026
e5c4624
build(ios): prepare responsive zoom test build
markm39 Nov 1, 2026
72b778e
Revert "build(ios): prepare responsive zoom test build"
markm39 Nov 1, 2026
55607b9
build(ios): prepare tested release candidate
markm39 Nov 1, 2026
c23f31c
docs(release): prepare OpenNotes 1.3.7 notes
markm39 Nov 1, 2026
e203064
fix(images): use system picker without full library access
markm39 Nov 1, 2026
2e5ae55
build(ios): prepare photo picker fix for testflight
markm39 Nov 1, 2026
85bc8e7
test(ios): capture overlay history errors in audit
markm39 Nov 1, 2026
3e36299
fix(editor): preserve undo after lasso cancellation
markm39 Nov 1, 2026
1827444
feat(library): default to compact folder layout
markm39 Nov 1, 2026
b1e41f5
test(export): wait for iOS share sheet actions
markm39 Nov 1, 2026
8134518
test(export): wait consistently for share actions
markm39 Nov 1, 2026
a9939de
feat(library): offer optional MathNotes discovery
markm39 Nov 1, 2026
a9541c7
test(ios): exercise invitation and visible page crossing
markm39 Nov 1, 2026
1015b5d
chore(release): prepare OpenNotes build 40 [skip ci]
markm39 Nov 1, 2026
300759f
chore(release): remove unused engine archives [skip ci]
markm39 Nov 1, 2026
2ddacf7
test(canvas): wait for an idle audit host
markm39 Nov 2, 2026
e68b08d
test(canvas): measure host CPU idle directly
markm39 Nov 2, 2026
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
197 changes: 197 additions & 0 deletions .github/workflows/canvas-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
name: Native Canvas Stall Audit

on:
pull_request:
paths:
- 'ios/**'
- 'src/**'
- 'app/**'
- 'vendor/**'
- 'package*.json'
- 'scripts/ci/**'
- 'scripts/ios/**'
- '.github/workflows/canvas-audit.yml'
workflow_dispatch:

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
canvas-interaction-audit:
name: iPad canvas interaction stall audit
runs-on: macos-15
timeout-minutes: 80
env:
IOS_SIMULATOR_FAMILY: ipad
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci --legacy-peer-deps
- run: node --test scripts/ci/verify-canvas-audit.test.mjs
- name: Select compatible native audit toolchain
run: echo "DEVELOPER_DIR=$(python3 scripts/ci/select-ios-toolchain.py)" >> "$GITHUB_ENV"
- name: Install iOS pods
run: pod install --project-directory=ios
- name: Resolve iPad simulator
run: echo "IOS_SIMULATOR_DESTINATION=$(node scripts/ci/resolve-ios-simulator.mjs)" >> "$GITHUB_ENV"
- name: Record toolchain and host conditions
run: |
xcodebuild -version
uname -m
uptime
xcrun simctl list runtimes
- name: Build native interaction tests
run: |
UITEST_FORCE_BUNDLING=1 xcodebuild build-for-testing \
-workspace ios/OpenNotes.xcworkspace -scheme OpenNotesIntegrity \
-derivedDataPath /tmp/opennotes-ui-deriveddata \
-destination "$IOS_SIMULATOR_DESTINATION" -parallel-testing-enabled NO \
ONLY_ACTIVE_ARCH=YES IPHONEOS_DEPLOYMENT_TARGET=15.1 COMPILER_INDEX_STORE_ENABLE=NO
- name: Seed owned 100-page lecture fixtures
run: |
simulator_id="${IOS_SIMULATOR_DESTINATION##*id=}"
python3 -c 'import subprocess,sys; subprocess.run(["xcrun", "simctl", "bootstatus", sys.argv[1], "-b"], check=True, timeout=180)' "$simulator_id"
xcrun simctl install "$simulator_id" /tmp/opennotes-ui-deriveddata/Build/Products/Release-iphonesimulator/OpenNotes.app
xcrun simctl addmedia "$simulator_id" assets/icon.png
xcrun simctl privacy "$simulator_id" grant photos com.builderpro.opennotes
python3 scripts/seed-native-audit.py "$simulator_id"
- name: Inspect photo picker
continue-on-error: true
run: |
xcodebuild test-without-building \
-workspace ios/OpenNotes.xcworkspace -scheme OpenNotesIntegrity \
-derivedDataPath /tmp/opennotes-ui-deriveddata \
-destination "$IOS_SIMULATOR_DESTINATION" -parallel-testing-enabled NO \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testChoosePhotoCover \
-collect-test-diagnostics never > "$RUNNER_TEMP/photo-picker-test.log" 2>&1
- name: Preserve photo picker output
if: always()
uses: actions/upload-artifact@v4
with:
name: photo-picker-diagnostic
path: ${{ runner.temp }}/photo-picker-test.log
if-no-files-found: warn
retention-days: 7
- name: Wait for the simulator host to settle
run: node scripts/ci/wait-for-audit-host.mjs
- name: Run real native interaction workloads
id: interaction_workloads
run: |
simulator_id="${IOS_SIMULATOR_DESTINATION##*id=}"
xcrun simctl spawn "$simulator_id" log stream --style compact --level debug \
--predicate 'process == "OpenNotes" AND eventMessage CONTAINS "[OverlayHistory]"' \
> "$RUNNER_TEMP/overlay-history.log" 2>&1 &
log_pid=$!
trap 'kill "$log_pid" 2>/dev/null || true; wait "$log_pid" 2>/dev/null || true' EXIT
xcodebuild test-without-building \
-workspace ios/OpenNotes.xcworkspace -scheme OpenNotesIntegrity \
-derivedDataPath /tmp/opennotes-ui-deriveddata \
-destination "$IOS_SIMULATOR_DESTINATION" -parallel-testing-enabled NO \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDensePagesAndKeyboardInteractionAudit \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDensePanZoomInteractionAudit \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDenseWritingDuringAutosaveInteractionAudit \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testStrokeCrossesPageBoundary \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testCrossPageAssetHistory \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testDenseEraserHistoryRemainsEditable \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testLibraryGeneratesMissingInkThumbnail \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testMathNotesInvitationCanBeDismissedPermanently \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testCrossPageTextHistory \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testRotatedImageResizeAndCrop \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testDrawingOverImageSurvivesUndoAndReload \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testInvalidNativePayloadShowsRecoveryWithoutOverwritingNote \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testPreferencesAndReturnToPen \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testChoosePhotoCover \
-collect-test-diagnostics never
- name: Preserve interaction test evidence
if: ${{ always() && (steps.interaction_workloads.outcome == 'success' || steps.interaction_workloads.outcome == 'failure') }}
uses: actions/upload-artifact@v4
with:
name: canvas-interaction-results
path: /tmp/opennotes-ui-deriveddata/Logs/Test
retention-days: 7
- name: Preserve overlay history diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: overlay-history-diagnostics
path: ${{ runner.temp }}/overlay-history.log
if-no-files-found: ignore
retention-days: 7
- name: Record repeated native zoom interactions
if: ${{ always() && (steps.interaction_workloads.outcome == 'success' || steps.interaction_workloads.outcome == 'failure') }}
run: |
simulator_id="${IOS_SIMULATOR_DESTINATION##*id=}"
xcrun simctl io "$simulator_id" recordVideo "$RUNNER_TEMP/zoom-interactions.mp4" > "$RUNNER_TEMP/zoom-recording.log" 2>&1 &
recording_pid=$!
trap 'kill -INT "$recording_pid" 2>/dev/null || true; wait "$recording_pid" || true' EXIT
xcodebuild test-without-building \
-workspace ios/OpenNotes.xcworkspace -scheme OpenNotesIntegrity \
-derivedDataPath /tmp/opennotes-ui-deriveddata \
-destination "$IOS_SIMULATOR_DESTINATION" -parallel-testing-enabled NO \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testDenseInteractionAudit \
-collect-test-diagnostics never
- name: Verify persisted lecture content
if: ${{ always() && (steps.interaction_workloads.outcome == 'success' || steps.interaction_workloads.outcome == 'failure') }}
run: |
simulator_id="${IOS_SIMULATOR_DESTINATION##*id=}"
python3 -c 'import subprocess,sys; subprocess.run(["xcrun", "simctl", "bootstatus", sys.argv[1], "-b"], check=True, timeout=180)' "$simulator_id"
python3 scripts/verify-native-audit.py "$simulator_id"
- name: Validate native responsiveness and render budgets
id: canvas_budgets
if: always()
run: |
python3 scripts/ci/collect-canvas-audits.py "$RUNNER_TEMP/canvas-audit-reports"
node scripts/ci/verify-canvas-audit.mjs "$RUNNER_TEMP/canvas-audit-reports"
- name: Preserve initial interaction evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: canvas-initial-interaction-audit
path: |
/tmp/opennotes-ui-deriveddata/Logs/Test
${{ runner.temp }}/canvas-audit-reports
${{ runner.temp }}/zoom-interactions.mp4
${{ runner.temp }}/zoom-recording.log
if-no-files-found: warn
retention-days: 7
- name: Profile failed native workloads
if: ${{ failure() && (steps.interaction_workloads.outcome == 'failure' || (steps.interaction_workloads.outcome == 'success' && steps.canvas_budgets.outcome == 'failure')) }}
run: |
# Restore known geometry before rerunning the edit/relaunch case.
# Timing reports above remain the unprofiled evidence.
simulator_id="${IOS_SIMULATOR_DESTINATION##*id=}"
app_data=$(xcrun simctl get_app_container "$simulator_id" com.builderpro.opennotes data)
mkdir -p "$RUNNER_TEMP/canvas-audit-profiles"
cp -R "$app_data/Documents/notebook-bodies" "$RUNNER_TEMP/canvas-audit-profiles/before-profile-bodies"
python3 scripts/seed-native-audit.py "$simulator_id"
python3 scripts/ci/sample-canvas-process.py OpenNotes "$RUNNER_TEMP/canvas-audit-profiles" &
profile_pid=$!
trap 'kill "$profile_pid" 2>/dev/null || true; wait "$profile_pid" || true' EXIT
xcodebuild test-without-building \
-workspace ios/OpenNotes.xcworkspace -scheme OpenNotesIntegrity \
-derivedDataPath /tmp/opennotes-ui-deriveddata \
-destination "$IOS_SIMULATOR_DESTINATION" -parallel-testing-enabled NO \
-only-testing:OpenNotesUITests/NotebookInteractionUITests/testCrossPageAssetHistory \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDensePagesAndKeyboardInteractionAudit \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDensePanZoomInteractionAudit \
-only-testing:OpenNotesUITests/NotebookStallUITests/testDenseWritingDuringAutosaveInteractionAudit \
-collect-test-diagnostics never
- name: Preserve audit evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: canvas-interaction-audit
path: |
/tmp/opennotes-ui-deriveddata/Logs/Test
${{ runner.temp }}/canvas-audit-reports
${{ runner.temp }}/canvas-audit-profiles
${{ runner.temp }}/zoom-interactions.mp4
${{ runner.temp }}/zoom-recording.log
if-no-files-found: warn
retention-days: 7
4 changes: 2 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"expo": {
"name": "OpenNotes",
"slug": "open-notes",
"version": "1.3.6",
"version": "1.3.7",
"scheme": "opennotes",
"orientation": "default",
"icon": "./assets/icon.png",
Expand All @@ -16,7 +16,7 @@
"ios": {
"bundleIdentifier": "com.builderpro.opennotes",
"icon": "./assets/icon.png",
"buildNumber": "20",
"buildNumber": "40",
"supportsTablet": true,
"usesIcloudStorage": true,
"entitlements": {
Expand Down
12 changes: 7 additions & 5 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Stack, useRouter, type ErrorBoundaryProps } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Alert, Pressable, Share, StyleSheet, Text, useColorScheme, View } from 'react-native';
import { Alert, Pressable, Share, StyleSheet, Text, View } from 'react-native';
import { CrashReportBanner } from '../src/components/CrashReportBanner';
import * as Linking from 'expo-linking';
import { darkColors, lightColors } from '../src/theme/colors';
import { PreferencesProvider } from '../src/hooks/Preferences';
import { radius, spacing } from '../src/theme/spacing';
import { typography } from '../src/theme/typography';
import { useTheme } from '../src/hooks/useTheme';
Expand All @@ -27,9 +27,11 @@ const reportFromPreviousRun = takePendingCrashReport();
const handledPdfUrls = new Set<string>();

export default function RootLayout() {
const scheme = useColorScheme();
const isDark = scheme === 'dark';
const colors = isDark ? darkColors : lightColors;
return <PreferencesProvider><RootContent /></PreferencesProvider>;
}

function RootContent() {
const { isDark, colors } = useTheme();
useOpenInPdfImport();
const [pendingReport, setPendingReport] = useState<CrashReport | null>(reportFromPreviousRun);
const dismissReport = useCallback(() => setPendingReport(null), []);
Expand Down
5 changes: 5 additions & 0 deletions app/folder/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { RenameDialog } from '../../src/components/library/RenameDialog';
import { FolderPickerSheet } from '../../src/components/library/FolderPickerSheet';
import { CreateNoteBackgroundSheet } from '../../src/components/library/CreateNoteBackgroundSheet';
import { useTheme } from '../../src/hooks/useTheme';
import { useNoteCover } from '../../src/hooks/useNoteCover';
import { spacing } from '../../src/theme/spacing';
import {
createNote,
Expand Down Expand Up @@ -59,6 +60,7 @@ export default function FolderScreen() {
setAllFolders(folders);
setLoading(false);
}, [id]);
const { chooseCover, removeCover, presentPendingCover } = useNoteCover(refresh);

useEffect(() => {
void refresh();
Expand Down Expand Up @@ -189,10 +191,13 @@ export default function FolderScreen() {

<ItemActionsMenu
visible={action?.kind === 'noteMenu'}
onDismiss={presentPendingCover}
title={action?.kind === 'noteMenu' ? action.note.title : undefined}
actions={
action?.kind === 'noteMenu'
? [
{ key: 'cover', label: t.preferences.chooseCover, onPress: () => chooseCover(action.note) },
...(action.note.customCoverUri ? [{ key: 'removeCover', label: t.preferences.removeCover, onPress: () => void removeCover(action.note) }] : []),
{
key: 'rename',
label: t.common.rename,
Expand Down
Loading
Loading