From b7aa004686eabb2365d31a13a5e9060f0b7ecb6f Mon Sep 17 00:00:00 2001 From: tianma-if Date: Fri, 31 Jul 2026 11:02:17 +0800 Subject: [PATCH] feat(mobile): add web clipping and revision safeguards --- apps/mobile/app.json | 22 ++ apps/mobile/app/+native-intent.ts | 7 + apps/mobile/app/index.tsx | 7 +- apps/mobile/package.json | 1 + apps/mobile/src/lib/mobile-locale.tsx | 9 + apps/mobile/src/lib/mobile-web-clip.test.ts | 64 +++++ apps/mobile/src/lib/mobile-web-clip.ts | 292 +++++++++++++++++++ apps/mobile/src/lib/sync-queue.test.ts | 24 ++ apps/mobile/src/screens/WorkspaceScreen.tsx | 295 ++++++++++++++++++-- bun.lock | 123 ++++++-- tests/mobile-app-scope.test.ts | 8 + 11 files changed, 809 insertions(+), 43 deletions(-) create mode 100644 apps/mobile/app/+native-intent.ts create mode 100644 apps/mobile/src/lib/mobile-web-clip.test.ts create mode 100644 apps/mobile/src/lib/mobile-web-clip.ts diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 17dd75a4..00b572d2 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -58,6 +58,28 @@ "enableShrinkResourcesInReleaseBuilds": true } } + ], + [ + "expo-sharing", + { + "ios": { + "enabled": true, + "activationRule": { + "supportsText": true, + "supportsWebPageWithMaxCount": 1, + "supportsWebUrlWithMaxCount": 1 + } + }, + "android": { + "enabled": true, + "multipleShareMimeTypes": [ + "text/plain" + ], + "singleShareMimeTypes": [ + "text/plain" + ] + } + } ] ], "experiments": { diff --git a/apps/mobile/app/+native-intent.ts b/apps/mobile/app/+native-intent.ts new file mode 100644 index 00000000..abe90fe2 --- /dev/null +++ b/apps/mobile/app/+native-intent.ts @@ -0,0 +1,7 @@ +export const redirectSystemPath = ({ path }: { path: string; initial: boolean }) => { + try { + return new URL(path).hostname === "expo-sharing" ? "/" : path; + } catch { + return "/"; + } +}; diff --git a/apps/mobile/app/index.tsx b/apps/mobile/app/index.tsx index 73eb2056..724a4f57 100644 --- a/apps/mobile/app/index.tsx +++ b/apps/mobile/app/index.tsx @@ -1,4 +1,5 @@ import * as SplashScreen from "expo-splash-screen"; +import { useIncomingShare } from "expo-sharing"; import { useIsRestoring } from "@tanstack/react-query"; import { lazy, Suspense, useEffect } from "react"; import { ActivityIndicator, StyleSheet, View } from "react-native"; @@ -15,6 +16,7 @@ const WorkspaceScreen = lazy(() => export default function IndexScreen() { const { isLoading, session } = useSession(); const isRestoringCache = useIsRestoring(); + const { clearSharedPayloads, sharedPayloads } = useIncomingShare(); useEffect(() => { if (!isLoading && !isRestoringCache) { @@ -29,7 +31,10 @@ export default function IndexScreen() { return session ? ( }> - + ) : ( diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 8a3dd3bc..2bc7c3b7 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -35,6 +35,7 @@ "expo-image-manipulator": "~57.0.5", "expo-router": "latest", "expo-secure-store": "latest", + "expo-sharing": "~57.0.8", "expo-splash-screen": "^57.0.4", "expo-sqlite": "~57.0.1", "expo-status-bar": "latest", diff --git a/apps/mobile/src/lib/mobile-locale.tsx b/apps/mobile/src/lib/mobile-locale.tsx index c46fa173..3a4626f9 100644 --- a/apps/mobile/src/lib/mobile-locale.tsx +++ b/apps/mobile/src/lib/mobile-locale.tsx @@ -31,6 +31,15 @@ const mobileOnlyTranslations = new Map([ ["输入关键词开始搜索", "Enter a keyword to search"], ["搜索本机同步缓存,结果会即时显示", "Search the local synced cache with instant results"], ["笔记操作", "Note actions"], + ["版本历史", "Version history"], + ["同步冲突", "Sync conflict"], + ["同步失败", "Sync failed"], + ["待同步", "Pending sync"], + ["已同步", "Synced"], + ["查看并处理同步冲突", "Review and resolve the sync conflict"], + ["云端笔记已在其他设备更新,本地修改没有覆盖云端。你可以查看版本历史,或放弃本地修改并使用云端版本。", "The cloud note was updated on another device. Your local changes did not overwrite it. View version history, or discard the local changes and use the cloud version."], + ["查看历史", "View history"], + ["使用云端版本", "Use cloud version"], ["加载失败", "Failed to load"], ["请稍后重试", "Please try again later"], ["重试", "Retry"], diff --git a/apps/mobile/src/lib/mobile-web-clip.test.ts b/apps/mobile/src/lib/mobile-web-clip.test.ts new file mode 100644 index 00000000..6ae04b56 --- /dev/null +++ b/apps/mobile/src/lib/mobile-web-clip.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from "bun:test"; +import { + buildMobileWebClipDraft, + extractPageTitle, + getSharedWebUrl, + htmlToMarkdown, + isWeChatArticleUrl, +} from "./mobile-web-clip"; + +describe("mobile web clip", () => { + test("extracts a WeChat URL embedded in shared text", () => { + expect(getSharedWebUrl([ + { shareType: "text", value: "一篇文章\nhttps://mp.weixin.qq.com/s/abc123" }, + ])).toBe("https://mp.weixin.qq.com/s/abc123"); + expect(isWeChatArticleUrl("https://mp.weixin.qq.com/s/abc123")).toBe(true); + expect(isWeChatArticleUrl("http://mp.weixin.qq.com/s/abc123")).toBe(false); + }); + + test("extracts title regardless of meta attribute order", () => { + expect(extractPageTitle('')) + .toBe("公众号文章标题 & 更多"); + }); + + test("turns common article HTML into readable markdown", () => { + const markdown = htmlToMarkdown(` +
+

第一节

+

正文重点
下一行

+ 示例图 +

阅读更多

+
+ `); + expect(markdown).toContain("## 第一节"); + expect(markdown).toContain("正文**重点**\n下一行"); + expect(markdown).toContain("![示例图](https://mmbiz.qpic.cn/example.jpg)"); + expect(markdown).toContain("[阅读更多](https://example.com)"); + }); + + test("builds a complete WeChat clip from nested js_content", async () => { + const html = ` + +

开头

嵌套正文

+ `; + const draft = await buildMobileWebClipDraft("https://mp.weixin.qq.com/s/test", { + capturedAt: new Date("2026-07-31T00:00:00.000Z"), + fetcher: async () => new Response(html, { status: 200 }), + }); + expect(draft.title).toBe("测试文章"); + expect(draft.tagsText).toBe("web-clip, wechat"); + expect(draft.contentMarkdown).toContain("开头"); + expect(draft.contentMarkdown).toContain("嵌套正文"); + expect(draft.contentMarkdown).toContain("2026-07-31T00:00:00.000Z"); + }); + + test("keeps the source URL when fetching fails", async () => { + const draft = await buildMobileWebClipDraft("https://mp.weixin.qq.com/s/offline", { + fetcher: async () => { + throw new Error("offline"); + }, + }); + expect(draft.contentMarkdown).toContain("https://mp.weixin.qq.com/s/offline"); + expect(draft.contentMarkdown).toContain("正文暂时无法抓取"); + }); +}); diff --git a/apps/mobile/src/lib/mobile-web-clip.ts b/apps/mobile/src/lib/mobile-web-clip.ts new file mode 100644 index 00000000..f02fc6a8 --- /dev/null +++ b/apps/mobile/src/lib/mobile-web-clip.ts @@ -0,0 +1,292 @@ +export type MobileSharedPayload = { + mimeType?: string; + shareType?: string; + value?: string; +}; + +export type MobileWebClipDraft = { + contentMarkdown: string; + sourceUrl: string; + tagsText: string; + title: string; +}; + +const URL_PATTERN = /https?:\/\/[^\s<>"'))]+/i; +const WECHAT_ARTICLE_HOSTS = new Set(["mp.weixin.qq.com"]); +const BLOCK_TAGS = new Set([ + "article", + "aside", + "blockquote", + "div", + "figure", + "figcaption", + "footer", + "header", + "main", + "p", + "section", + "table", + "tbody", + "td", + "th", + "thead", + "tr", +]); + +const HTML_ENTITIES: Record = { + amp: "&", + apos: "'", + gt: ">", + hellip: "…", + ldquo: "“", + lsquo: "‘", + lt: "<", + middot: "·", + nbsp: " ", + ndash: "–", + quot: "\"", + rdquo: "”", + rsquo: "’", +}; + +export const getSharedWebUrl = (payloads: MobileSharedPayload[]) => { + for (const payload of payloads) { + const value = payload.value?.trim() ?? ""; + const match = value.match(URL_PATTERN); + if (!match) { + continue; + } + try { + const url = new URL(match[0]); + if (url.protocol === "http:" || url.protocol === "https:") { + return url.toString(); + } + } catch { + // Ignore malformed text and continue looking through other payloads. + } + } + return null; +}; + +export const isWeChatArticleUrl = (value: string) => { + try { + const url = new URL(value); + return url.protocol === "https:" && WECHAT_ARTICLE_HOSTS.has(url.hostname.toLowerCase()) && url.pathname.startsWith("/s"); + } catch { + return false; + } +}; + +export const buildMobileWebClipDraft = async ( + sourceUrl: string, + options: { + fetcher?: typeof fetch; + capturedAt?: Date; + } = {} +): Promise => { + const fetcher = options.fetcher ?? fetch; + const capturedAt = options.capturedAt ?? new Date(); + let html = ""; + + try { + const response = await fetcher(sourceUrl, { + headers: { + Accept: "text/html,application/xhtml+xml", + }, + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + html = await response.text(); + } catch { + return buildFallbackDraft(sourceUrl, capturedAt); + } + + const title = extractPageTitle(html) || hostnameTitle(sourceUrl); + const articleHtml = isWeChatArticleUrl(sourceUrl) + ? extractElementInnerHtmlById(html, "js_content") + : extractFirstElementInnerHtml(html, ["article", "main"]); + const body = htmlToMarkdown(articleHtml || ""); + + if (!body) { + return { + ...buildFallbackDraft(sourceUrl, capturedAt), + title, + }; + } + + return { + title, + sourceUrl, + tagsText: isWeChatArticleUrl(sourceUrl) ? "web-clip, wechat" : "web-clip", + contentMarkdown: [ + `来源:[${escapeMarkdownText(sourceUrl)}](${sourceUrl})`, + `剪藏时间:${capturedAt.toISOString()}`, + "---", + body, + ].join("\n\n"), + }; +}; + +export const extractPageTitle = (html: string) => { + for (const property of ["og:title", "twitter:title"]) { + const meta = findMetaContent(html, property); + if (meta) { + return normalizeInlineText(meta); + } + } + + const activityTitle = extractElementInnerHtmlById(html, "activity-name"); + if (activityTitle) { + return normalizeInlineText(stripTags(activityTitle)); + } + + const titleMatch = html.match(/]*>([\s\S]*?)<\/title\s*>/i); + return titleMatch ? normalizeInlineText(stripTags(titleMatch[1])) : ""; +}; + +export const htmlToMarkdown = (html: string) => { + if (!html.trim()) { + return ""; + } + + let value = html + .replace(//g, "") + .replace(/<(script|style|noscript|template|iframe|svg|canvas)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, ""); + + value = value.replace(/]*>/gi, (tag) => { + const src = readAttribute(tag, "data-src") || readAttribute(tag, "data-original") || readAttribute(tag, "src"); + if (!src || src.startsWith("data:")) { + return ""; + } + const alt = normalizeInlineText(readAttribute(tag, "alt") || "图片"); + return `\n\n![${escapeMarkdownText(alt)}](${src})\n\n`; + }); + + value = value.replace(/]*>([\s\S]*?)<\/a\s*>/gi, (full, inner: string) => { + const href = readAttribute(full, "href"); + const label = normalizeInlineText(stripTags(inner)); + if (!href || /^javascript:/i.test(href)) { + return label; + } + return `[${escapeMarkdownText(label || href)}](${href})`; + }); + + value = value + .replace(/]*>/gi, (_tag, level: string) => `\n\n${"#".repeat(Number(level))} `) + .replace(/<\/h[1-6]\s*>/gi, "\n\n") + .replace(//gi, "\n") + .replace(/]*>/gi, "\n\n---\n\n") + .replace(/]*>/gi, "\n- ") + .replace(/<\/li\s*>/gi, "\n") + .replace(/<(strong|b)\b[^>]*>/gi, "**") + .replace(/<\/(strong|b)\s*>/gi, "**") + .replace(/<(em|i)\b[^>]*>/gi, "*") + .replace(/<\/(em|i)\s*>/gi, "*") + .replace(/]*>/gi, "`") + .replace(/<\/code\s*>/gi, "`") + .replace(/<\/?blockquote\b[^>]*>/gi, "\n\n> ") + .replace(/<\/?([a-z][a-z0-9-]*)\b[^>]*>/gi, (tag, name: string) => + BLOCK_TAGS.has(name.toLowerCase()) ? "\n\n" : tag.startsWith(" ({ + title: hostnameTitle(sourceUrl), + sourceUrl, + tagsText: isWeChatArticleUrl(sourceUrl) ? "web-clip, wechat" : "web-clip", + contentMarkdown: [ + `来源:[${escapeMarkdownText(sourceUrl)}](${sourceUrl})`, + `剪藏时间:${capturedAt.toISOString()}`, + "", + "正文暂时无法抓取,来源链接已保留,可稍后重试。", + ].join("\n\n"), +}); + +const hostnameTitle = (value: string) => { + try { + return new URL(value).hostname; + } catch { + return "网页剪藏"; + } +}; + +const findMetaContent = (html: string, property: string) => { + const tags = html.match(/]*>/gi) ?? []; + for (const tag of tags) { + const key = readAttribute(tag, "property") || readAttribute(tag, "name"); + if (key.toLowerCase() === property.toLowerCase()) { + return decodeHtmlEntities(readAttribute(tag, "content")); + } + } + return ""; +}; + +const extractFirstElementInnerHtml = (html: string, tags: string[]) => { + for (const tag of tags) { + const match = html.match(new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}\\s*>`, "i")); + if (match?.[1]) { + return match[1]; + } + } + return ""; +}; + +const extractElementInnerHtmlById = (html: string, id: string) => { + const openingTagPattern = /<([a-z][a-z0-9-]*)\b[^>]*>/gi; + let openingMatch: RegExpExecArray | null; + while ((openingMatch = openingTagPattern.exec(html))) { + if (readAttribute(openingMatch[0], "id") !== id) { + continue; + } + const tagName = openingMatch[1].toLowerCase(); + const contentStart = openingTagPattern.lastIndex; + const boundaryPattern = new RegExp(`<\\/?${tagName}\\b[^>]*>`, "gi"); + boundaryPattern.lastIndex = contentStart; + let depth = 1; + let boundaryMatch: RegExpExecArray | null; + while ((boundaryMatch = boundaryPattern.exec(html))) { + if (boundaryMatch[0].startsWith("")) { + depth += 1; + } + } + return html.slice(contentStart); + } + return ""; +}; + +const readAttribute = (tag: string, name: string) => { + const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = tag.match(new RegExp(`(?:^|\\s)${escapedName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i")); + return decodeHtmlEntities(match?.[1] ?? match?.[2] ?? match?.[3] ?? ""); +}; + +const stripTags = (value: string) => value.replace(/<[^>]*>/g, " "); +const normalizeInlineText = (value: string) => decodeHtmlEntities(value).replace(/\s+/g, " ").trim(); +const escapeMarkdownText = (value: string) => value.replace(/([\\[\]])/g, "\\$1"); + +const decodeHtmlEntities = (value: string) => + value.replace(/&(#x?[0-9a-f]+|[a-z][a-z0-9]+);?/gi, (entity, key: string) => { + if (key.startsWith("#x") || key.startsWith("#X")) { + const codePoint = Number.parseInt(key.slice(2), 16); + return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : entity; + } + if (key.startsWith("#")) { + const codePoint = Number.parseInt(key.slice(1), 10); + return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : entity; + } + return HTML_ENTITIES[key.toLowerCase()] ?? entity; + }); diff --git a/apps/mobile/src/lib/sync-queue.test.ts b/apps/mobile/src/lib/sync-queue.test.ts index c6aab5f5..f3c48537 100644 --- a/apps/mobile/src/lib/sync-queue.test.ts +++ b/apps/mobile/src/lib/sync-queue.test.ts @@ -1,4 +1,5 @@ import { beforeEach, expect, mock, test } from "bun:test"; +import { ApiRequestError } from "@edgeever/client"; import type { MemoDetail } from "@edgeever/shared"; const storage = new Map(); @@ -39,6 +40,29 @@ test("keeps pending updates isolated by instance", async () => { expect((await listMobileSyncQueueItems("https://two.example"))[0]?.payload.title).toBe("Second instance"); }); +test("keeps a stale mobile edit as an explicit conflict without overwriting the cloud note", async () => { + const scope = "https://one.example"; + await queueMobileMemoUpdate(scope, basePayload); + let updateCalled = false; + const client = { + createMemoEditSession: async () => ({ + editSession: { id: "edit-2", baseRevision: 2, baseContentHash: "hash-2" }, + }), + updateMemo: async () => { + updateCalled = true; + throw new ApiRequestError("Unexpected update", 500, "unexpected_update"); + }, + }; + + const result = await syncMobileQueuedChanges(client as never, scope); + const queued = (await listMobileSyncQueueItems(scope))[0]; + + expect(result.conflicted).toBe(1); + expect(updateCalled).toBe(false); + expect(queued?.status).toBe("conflict"); + expect(queued?.payload.contentMarkdown).toBe("first"); +}); + test("rebases a newer local save when an older save finishes syncing", async () => { await queueMobileMemoUpdate("https://one.example", basePayload); diff --git a/apps/mobile/src/screens/WorkspaceScreen.tsx b/apps/mobile/src/screens/WorkspaceScreen.tsx index 78912037..d2bea8b9 100644 --- a/apps/mobile/src/screens/WorkspaceScreen.tsx +++ b/apps/mobile/src/screens/WorkspaceScreen.tsx @@ -83,12 +83,14 @@ import { import { useMobileLocale } from "../lib/mobile-locale"; import { useSession } from "../lib/session"; import { + deleteMobileSyncQueueItem, getMobileSyncRetryDelay, listMobileSyncQueueItems, loadMobileSyncQueueSummary, queueMobileMemoCreate, queueMobileMemoUpdate, syncMobileQueuedChanges, + type MobileSyncQueueItem, } from "../lib/sync-queue"; import { createMobileDataScope, @@ -112,6 +114,12 @@ import { resolveMobileThemeStyles, useMobileTheme, type MobileResolvedTheme } fr import { useMobileUpdate } from "../lib/mobile-update"; import { MobileMermaidDiagram, MobileMermaidProvider } from "../components/MobileMermaid"; import { getMobileMarkdownFenceLanguage, trimMobileMarkdownFenceContent } from "../lib/mobile-mermaid"; +import { + buildMobileWebClipDraft, + getSharedWebUrl, + type MobileSharedPayload, + type MobileWebClipDraft, +} from "../lib/mobile-web-clip"; const ALL_NOTES_ID = "all"; const DEFAULT_MEMO_TITLE = "无标题笔记"; @@ -167,7 +175,13 @@ type RichEditingSession = { }; type MobileMemoUpdateMutation = UseMutationResult; -export const WorkspaceScreen = () => { +export const WorkspaceScreen = ({ + incomingSharePayloads = [], + onIncomingShareHandled, +}: { + incomingSharePayloads?: MobileSharedPayload[]; + onIncomingShareHandled?: () => void; +}) => { const { resolvedTheme } = useMobileTheme(); const { preference: localePreference, setPreference: setLocalePreference } = useMobileLocale(); refreshWorkspaceThemeStyles(resolvedTheme); @@ -187,6 +201,8 @@ export const WorkspaceScreen = () => { const [selectedMemoId, setSelectedMemoId] = useState(null); const [searchText, setSearchText] = useState(""); const [createOpen, setCreateOpen] = useState(false); + const [incomingClipDraft, setIncomingClipDraft] = useState(null); + const [isImportingShare, setIsImportingShare] = useState(false); const [notesActionsOpen, setNotesActionsOpen] = useState(false); const [notebookPickerOpen, setNotebookPickerOpen] = useState(false); const [richEditingSession, setRichEditingSession] = useState(null); @@ -204,7 +220,18 @@ export const WorkspaceScreen = () => { const autoSyncRequestedRef = useRef(false); const autoSyncRetryTimerRef = useRef | null>(null); const memoDraftPrefetchRef = useRef(new Map>()); + const processedShareUrlRef = useRef(null); + const onIncomingShareHandledRef = useRef(onIncomingShareHandled); + onIncomingShareHandledRef.current = onIncomingShareHandled; + const [syncQueueItems, setSyncQueueItems] = useState([]); const debouncedSearchText = useDebouncedValue(searchText.trim(), 250); + const incomingShareUrl = useMemo(() => getSharedWebUrl(incomingSharePayloads), [incomingSharePayloads]); + + const refreshSyncQueueItems = useCallback(async () => { + const items = await listMobileSyncQueueItems(syncQueueScope); + setSyncQueueItems(items); + return items; + }, [syncQueueScope]); const notebooksQuery = useQuery({ queryKey: ["mobile", "notebooks"], @@ -489,16 +516,34 @@ export const WorkspaceScreen = () => { const openRichEditor = useCallback(async (memo: MemoDetail) => { beginEditorStartup(); - const draft = await loadMemoDraft(memo.id); + let editingMemo = memo; + const queuedItem = (await listMobileSyncQueueItems(syncQueueScope)).find((item) => item.memoId === memo.id); + + if (!queuedItem && client && !memo.id.startsWith("local:")) { + try { + const response = await client.getMemo(memo.id); + editingMemo = response.memo; + await upsertLocalMemo(dataScope, editingMemo); + queryClient.setQueryData(["mobile", "memo", "notebook", editingMemo.id], { memo: editingMemo }); + queryClient.setQueryData(["mobile", "memo", "trash", editingMemo.id], { memo: editingMemo }); + } catch { + // The local mirror remains editable while offline. + } + } + + const draft = await loadMemoDraft(editingMemo.id); memoDraftPrefetchRef.current.delete(memo.id); - setRichEditingSession({ draft, memo }); - }, [loadMemoDraft]); + setRichEditingSession({ draft, memo: editingMemo }); + }, [client, dataScope, loadMemoDraft, queryClient, syncQueueScope]); const memos = useMemo(() => memosQuery.data?.pages.flatMap((page) => page.memos) ?? [], [memosQuery.data]); const searchResults = useMemo(() => searchQuery.data?.pages.flatMap((page) => page.memos) ?? [], [searchQuery.data]); const searchActive = searchText.trim().length > 0; const visibleMemos = searchActive ? searchResults : memos; const selectedMemo = memoDetailQuery.data?.memo ?? null; + const selectedMemoSyncStatus = selectedMemo + ? syncQueueItems.find((item) => item.memoId === selectedMemo.id)?.status ?? null + : null; const isRefreshing = notebooksQuery.isFetching || memosQuery.isFetching || searchQuery.isFetching || memoDetailQuery.isFetching; const selectedMemoIdList = Array.from(selectedMemoIds); const selectedMemos = visibleMemos.filter((memo) => selectedMemoIds.has(memo.id)); @@ -515,9 +560,65 @@ export const WorkspaceScreen = () => { const canCreateMemo = memoView !== "trash" && Boolean(createMemoNotebookId); const openCreateMemo = () => { beginEditorStartup(); + setIncomingClipDraft(null); setCreateOpen(true); }; + useEffect(() => { + const sourceUrl = incomingShareUrl; + if (!sourceUrl) { + if (incomingSharePayloads.length > 0 && processedShareUrlRef.current !== "invalid-share") { + processedShareUrlRef.current = "invalid-share"; + Alert.alert("无法剪藏", "分享内容里没有可识别的网页链接。"); + onIncomingShareHandledRef.current?.(); + return; + } + processedShareUrlRef.current = null; + return; + } + if (processedShareUrlRef.current === sourceUrl) { + return; + } + if (notebooks.length === 0) { + if (notebooksQuery.isSuccess) { + processedShareUrlRef.current = sourceUrl; + Alert.alert("无法保存剪藏", "请先在 EdgeEver 中创建一个笔记本。"); + onIncomingShareHandledRef.current?.(); + } + return; + } + + let active = true; + processedShareUrlRef.current = sourceUrl; + setIsImportingShare(true); + void buildMobileWebClipDraft(sourceUrl) + .then((draft) => { + if (!active) { + return; + } + beginEditorStartup(); + setIncomingClipDraft(draft); + setActiveView("notes"); + setMemoView("notebook"); + setCreateOpen(true); + }) + .catch(() => { + if (active) { + Alert.alert("剪藏失败", "无法读取分享的网页,请稍后重试。"); + } + }) + .finally(() => { + if (active) { + setIsImportingShare(false); + onIncomingShareHandledRef.current?.(); + } + }); + + return () => { + active = false; + }; + }, [incomingSharePayloads.length, incomingShareUrl, notebooks.length, notebooksQuery.isSuccess]); + useEffect(() => { if (selectedMemo && !selectedMemo.isDeleted) { void loadMemoDraft(selectedMemo.id); @@ -531,7 +632,7 @@ export const WorkspaceScreen = () => { useEffect(() => { let mounted = true; - listMobileSyncQueueItems(syncQueueScope).then((items) => { + refreshSyncQueueItems().then((items) => { if (!mounted) { return; } @@ -548,7 +649,7 @@ export const WorkspaceScreen = () => { return () => { mounted = false; }; - }, [queryClient, syncQueueScope]); + }, [queryClient, refreshSyncQueueItems]); useEffect(() => { let mounted = true; @@ -646,6 +747,7 @@ export const WorkspaceScreen = () => { notebookId: optimisticMemo.notebookId, tags: optimisticMemo.tags, }); + await refreshSyncQueueItems(); return optimisticMemo; }, @@ -751,6 +853,50 @@ export const WorkspaceScreen = () => { ]); }; + const handleMemoSyncConflict = (memo: MemoDetail) => { + Alert.alert( + "同步冲突", + "云端笔记已在其他设备更新,本地修改没有覆盖云端。你可以查看版本历史,或放弃本地修改并使用云端版本。", + [ + { text: "取消", style: "cancel" }, + { text: "查看历史", onPress: () => setRevisionMemo(memo) }, + { + text: "使用云端版本", + style: "destructive", + onPress: () => { + void (async () => { + if (!client) { + return; + } + + try { + const conflict = (await listMobileSyncQueueItems(syncQueueScope)) + .find((item) => item.memoId === memo.id && item.status === "conflict"); + const response = await client.getMemo(memo.id); + if (conflict) { + await deleteMobileSyncQueueItem(syncQueueScope, conflict.id); + } + await Promise.all([ + clearMobileMemoDraft(memo.id), + upsertLocalMemo(dataScope, response.memo), + ]); + queryClient.setQueryData(["mobile", "memo", "notebook", memo.id], response); + queryClient.setQueryData(["mobile", "memo", "trash", memo.id], response); + await Promise.all([ + refreshSyncQueueItems(), + queryClient.invalidateQueries({ queryKey: ["mobile", "memos"] }), + queryClient.invalidateQueries({ queryKey: ["mobile", "search"] }), + ]); + } catch (error) { + Alert.alert("加载失败", error instanceof Error ? error.message : "请稍后重试"); + } + })(); + }, + }, + ] + ); + }; + const handleDeleteSelection = () => { const permanent = memoView === "trash"; if (!permanent) { @@ -792,6 +938,7 @@ export const WorkspaceScreen = () => { const summary = await loadMobileSyncQueueSummary(syncQueueScope); if (summary.pending + summary.error + summary.syncing === 0) { + await refreshSyncQueueItems(); autoSyncRunningRef.current = false; return; } @@ -816,6 +963,7 @@ export const WorkspaceScreen = () => { } catch { // The queue keeps its retry metadata; the next scheduled pass resumes it. } finally { + await refreshSyncQueueItems(); autoSyncRunningRef.current = false; if (autoSyncRequestedRef.current) { autoSyncRequestedRef.current = false; @@ -992,14 +1140,16 @@ export const WorkspaceScreen = () => { isDeleting={deleteMemoMutation.isPending} isLoading={memoDetailQuery.isLoading} isRestoring={restoreMemoMutation.isPending} - isSaving={updateMemoMutation.isPending} + isSaving={updateMemoMutation.isPending || localUpdateMemoMutation.isPending} memo={selectedMemo} notebookName={notebooks.find((notebook) => notebook.id === selectedMemo?.notebookId)?.name ?? "未分类"} onClose={closeDetail} onDelete={handleDeleteMemo} onRichEdit={(memo) => void openRichEditor(memo)} onOpenRevisions={setRevisionMemo} + onResolveSyncConflict={handleMemoSyncConflict} onRestore={(memo) => restoreMemoMutation.mutate(memo)} + syncStatus={selectedMemoSyncStatus} visible={Boolean(selectedMemoId)} /> @@ -1027,7 +1177,17 @@ export const WorkspaceScreen = () => { {revisionMemo ? setRevisionMemo(null)} - onRestored={(memo) => { + onRestored={async (memo) => { + const queuedItems = (await listMobileSyncQueueItems(syncQueueScope)) + .filter((item) => item.memoId === memo.id); + await Promise.all([ + ...queuedItems.map((item) => deleteMobileSyncQueueItem(syncQueueScope, item.id)), + clearMobileMemoDraft(memo.id), + upsertLocalMemo(dataScope, memo), + ]); + queryClient.setQueryData(["mobile", "memo", "notebook", memo.id], { memo }); + queryClient.setQueryData(["mobile", "memo", "trash", memo.id], { memo }); + await refreshSyncQueueItems(); setRevisionMemo(null); setSelectedMemoId(memo.id); }} @@ -1038,9 +1198,11 @@ export const WorkspaceScreen = () => { dataScope={dataScope} defaultNotebookId={createMemoNotebookId} imageCompressionEnabled={imageCompressionEnabled} + initialDraft={incomingClipDraft} notebooks={notebooks} onCreated={() => { setCreateOpen(false); + setIncomingClipDraft(null); setActiveView("notes"); setMemoView("notebook"); setSelectedMemoId(null); @@ -1050,6 +1212,16 @@ export const WorkspaceScreen = () => { visible={createOpen} /> + + + + + 正在剪藏文章 + 正在提取标题、正文和图片链接… + + + + {selectionMoveOpen ? void; onQueued: () => void | Promise; @@ -1895,6 +2069,21 @@ const CreateMemoModal = ({ let active = true; setDraftLoaded(false); setEditorReady(false); + if (initialDraft) { + const markdown = initialDraft.contentMarkdown; + contentMarkdownRef.current = markdown; + contentJsonRef.current = markdownToDoc(markdown); + draftVersionRef.current = 0; + setTitle(initialDraft.title); + setTagsText(initialDraft.tagsText); + setContentMarkdown(markdown); + setNotebookId(fallbackNotebookId); + setDirty(false); + setDraftLoaded(true); + return () => { + active = false; + }; + } void readMobileNewMemoDraft(dataScope).then((draft) => { if (!active) { return; @@ -1916,10 +2105,10 @@ const CreateMemoModal = ({ return () => { active = false; }; - }, [fallbackNotebookId, visible]); + }, [dataScope, fallbackNotebookId, initialDraft, visible]); useEffect(() => { - if (!visible || !draftLoaded || !dirty) { + if (!visible || !draftLoaded || !dirty || initialDraft) { return; } const draftVersion = draftVersionRef.current; @@ -1949,7 +2138,7 @@ const CreateMemoModal = ({ }); }, 350); return () => clearTimeout(timeout); - }, [dataScope, dirty, draftLoaded, tagsText, targetNotebookId, title, visible, contentMarkdown]); + }, [dataScope, dirty, draftLoaded, initialDraft, tagsText, targetNotebookId, title, visible, contentMarkdown]); const createMutation = useMutation({ mutationFn: async () => { @@ -2028,7 +2217,9 @@ const CreateMemoModal = ({ materializedMemoRef.current = null; draftVersionRef.current += 1; setDirty(false); - await clearMobileNewMemoDraft(dataScope); + if (!initialDraft) { + await clearMobileNewMemoDraft(dataScope); + } if (materializedMemoId) { await clearMobileMemoDraft(materializedMemoId); } @@ -2054,7 +2245,9 @@ const CreateMemoModal = ({ }); materializedMemoRef.current = response.memo; await upsertLocalMemo(dataScope, response.memo); - await clearMobileNewMemoDraft(dataScope); + if (!initialDraft) { + await clearMobileNewMemoDraft(dataScope); + } await Promise.all([ queryClient.invalidateQueries({ queryKey: ["mobile", "notebooks"] }), queryClient.invalidateQueries({ queryKey: ["mobile", "memos"] }), @@ -2349,7 +2542,7 @@ const RevisionHistoryModal = ({ }: { memo: MemoDetail | null; onClose: () => void; - onRestored: (memo: MemoDetail) => void; + onRestored: (memo: MemoDetail) => void | Promise; }) => { const { client } = useSession(); const queryClient = useQueryClient(); @@ -2403,7 +2596,7 @@ const RevisionHistoryModal = ({ queryClient.invalidateQueries({ queryKey: ["mobile", "memo"] }), queryClient.invalidateQueries({ queryKey: ["mobile", "memo-revisions", restoredMemo.id] }), ]); - onRestored(restoredMemo); + await onRestored(restoredMemo); }, }); @@ -2657,7 +2850,9 @@ const MemoDetailModal = ({ onDelete, onRichEdit, onOpenRevisions, + onResolveSyncConflict, onRestore, + syncStatus, visible, }: { isDeleting: boolean; @@ -2670,7 +2865,9 @@ const MemoDetailModal = ({ onDelete: (memo: MemoDetail) => void; onRichEdit: (memo: MemoDetail) => void; onOpenRevisions: (memo: MemoDetail) => void; + onResolveSyncConflict: (memo: MemoDetail) => void; onRestore: (memo: MemoDetail) => void; + syncStatus: MobileSyncQueueItem["status"] | null; visible: boolean; }) => { const { session } = useSession(); @@ -2770,6 +2967,15 @@ const MemoDetailModal = ({ const detailText = memo?.contentMarkdown || memo?.contentText || "没有正文内容"; const searchMatches = useMemo(() => getTextSearchMatches(detailText, searchQuery), [detailText, searchQuery]); const searchMatchLabel = searchQuery.trim() ? `${searchMatches.length > 0 ? activeMatchIndex + 1 : 0}/${searchMatches.length}` : "0/0"; + const syncStatusLabel = isSaving || syncStatus === "syncing" + ? "保存中" + : syncStatus === "conflict" + ? "同步冲突" + : syncStatus === "error" + ? "同步失败" + : syncStatus === "pending" + ? "待同步" + : "已同步"; const editFabBottom = Math.max( safeAreaInsets.bottom, Platform.OS === "android" ? ANDROID_SYSTEM_NAVIGATION_FALLBACK : 0 @@ -2800,7 +3006,30 @@ const MemoDetailModal = ({ - {isSaving ? "保存中" : "已保存"} + memo && onResolveSyncConflict(memo)} + > + + {syncStatusLabel} + + + {memo && !memo.isDeleted ? ( + onOpenRevisions(memo)} + style={styles.detailHeaderIconButton} + > + + + ) : null} {memo?.isDeleted ? ( setActionsOpen(true)} style={styles.detailHeaderIconButton}> @@ -6090,6 +6319,11 @@ const baseWorkspaceStyles = StyleSheet.create({ paddingHorizontal: 8, paddingVertical: 4, }, + detailSyncStatusConflict: { + backgroundColor: "#fff7ed", + color: "#c2410c", + fontWeight: "700", + }, detailMetaRow: { alignItems: "center", flexDirection: "row", @@ -6485,6 +6719,35 @@ const baseWorkspaceStyles = StyleSheet.create({ flex: 1, justifyContent: "center", }, + shareImportBackdrop: { + alignItems: "center", + backgroundColor: "rgba(15, 23, 42, 0.42)", + flex: 1, + justifyContent: "center", + padding: 24, + }, + shareImportCard: { + alignItems: "center", + backgroundColor: "#ffffff", + borderRadius: 18, + gap: 10, + maxWidth: 340, + paddingHorizontal: 28, + paddingVertical: 26, + width: "100%", + }, + shareImportTitle: { + color: "#0f172a", + fontSize: 17, + fontWeight: "800", + marginTop: 4, + }, + shareImportDescription: { + color: "#64748b", + fontSize: 13, + lineHeight: 20, + textAlign: "center", + }, }); let styles = baseWorkspaceStyles; diff --git a/bun.lock b/bun.lock index e527453e..55b71df6 100644 --- a/bun.lock +++ b/bun.lock @@ -55,7 +55,7 @@ "@electron/asar": "3.4.1", "@types/marked": "^6.0.0", "@types/node": "latest", - "@types/react": "latest", + "@types/react": "^19.2.18", "@types/react-dom": "latest", "@types/turndown": "^5.0.6", "@vitejs/plugin-react": "latest", @@ -123,6 +123,7 @@ "expo-image-manipulator": "~57.0.5", "expo-router": "latest", "expo-secure-store": "latest", + "expo-sharing": "~57.0.8", "expo-splash-screen": "^57.0.4", "expo-sqlite": "~57.0.1", "expo-status-bar": "latest", @@ -723,7 +724,7 @@ "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="], - "@expo/ui": ["@expo/ui@57.0.7", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-worklets"] }, "sha512-WqRVabl8VpHf3+YLHVjUy7PMIuXXI6DG88Vgmavro7Nd8Ks13h9sEJH/RLSCaJE2daVnqzEKY1v797BibuY9aw=="], + "@expo/ui": ["@expo/ui@57.0.8", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-worklets"] }, "sha512-aR17CRM45k4JyFahX4Xn2b6ti3aiddC4SAzvsm3iUCVacQA6tGmClu012rYNeVXhJ96csX2o7EEymfzYqgJNxw=="], "@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="], @@ -985,15 +986,15 @@ "@react-native-masked-view/masked-view": ["@react-native-masked-view/masked-view@0.3.2", "", { "peerDependencies": { "react": ">=16", "react-native": ">=0.57" } }, "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ=="], - "@react-native/assets-registry": ["@react-native/assets-registry@0.86.0", "", {}, "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew=="], + "@react-native/assets-registry": ["@react-native/assets-registry@0.86.2", "", {}, "sha512-vcX/mBjWAVnWofu7KecotquI2unZ/tITwA7OGdq/mdY/zmGXIEvYhfEYyOQij/LRqi9WAL+iizInTBWnxDhK/Q=="], - "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="], + "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.2", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.2" } }, "sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g=="], "@react-native/babel-preset": ["@react-native/babel-preset@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@react-native/babel-plugin-codegen": "0.86.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA=="], - "@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="], + "@react-native/codegen": ["@react-native/codegen@0.86.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA=="], - "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.86.0", "", { "dependencies": { "@react-native/dev-middleware": "0.86.0", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.84.3", "metro-config": "^0.84.3", "metro-core": "^0.84.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "0.86.0" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ=="], + "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.86.2", "", { "dependencies": { "@react-native/dev-middleware": "0.86.2", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.84.3", "metro-config": "^0.84.3", "metro-core": "^0.84.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "0.86.2" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-YHXNKoM6Y/HjREySZ5arET2xgiHgg67r1MdwJB//MPJAJ0Xc5g0u6UHxY9VzsHO3Y07dre6s0BinYwjt1SEWvQ=="], "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.86.0", "", {}, "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw=="], @@ -1001,17 +1002,17 @@ "@react-native/dev-middleware": ["@react-native/dev-middleware@0.86.0", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.86.0", "@react-native/debugger-shell": "0.86.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw=="], - "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.86.0", "", {}, "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ=="], + "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.86.2", "", {}, "sha512-2F6x14NcHMpVmfTTFKfMkpV5dZedZrLiv6PE+c3vgnesV2bjleUBydr4U+NI8VkI7OwW71L0A5qQ76I9LCrfoQ=="], - "@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.0", "", {}, "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ=="], + "@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.2", "", {}, "sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ=="], "@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.86.0", "hermes-parser": "0.36.0", "nullthrows": "^1.1.1" } }, "sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ=="], "@react-native/metro-config": ["@react-native/metro-config@0.86.0", "", { "dependencies": { "@react-native/js-polyfills": "0.86.0", "@react-native/metro-babel-transformer": "0.86.0", "metro-config": "^0.84.3", "metro-runtime": "^0.84.3" } }, "sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw=="], - "@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.0", "", {}, "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g=="], + "@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.2", "", {}, "sha512-EzPFc9Y6lzYOWeso2almwXI7f8+qReHxWvT+algsOczb2UhWXIWXDoSvkdwoSfiwwmGt/ijJgKJoeHlzPkLwRg=="], - "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.0" }, "optionalPeers": ["@types/react"] }, "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw=="], + "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.2", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.2" }, "optionalPeers": ["@types/react"] }, "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="], @@ -1373,7 +1374,7 @@ "@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], - "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -1543,7 +1544,7 @@ "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="], - "babel-preset-expo": ["babel-preset-expo@57.0.4", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.0", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.6", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-EkFcNoE23HVzQT6ZNXs/adN8+G7rqEAF6tQn6LpRPYa1YY7wmX5GxhJF0kaYMNtBndNrMTN4+0rYE17VG13KFg=="], + "babel-preset-expo": ["babel-preset-expo@57.0.5", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.2", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.7", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-sz9ZBTiUAlu5P9VORVNKoeAaY2qKFQRC6eQV5Y8aMkqcorvXKEv97UeCkFMLmMBEPKA+QeWImREKkX9/H21WQA=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], @@ -2109,12 +2110,14 @@ "expo-modules-jsi": ["expo-modules-jsi@57.0.3", "", { "peerDependencies": { "react-native": "*" } }, "sha512-B+iXJCC5OIXjFKkLLSY2DZ8BGS+hC3PBTrALpV43pHqhXqqZrRH3uTEylZgsoKQO8N8tjmjAua2ucTFHtr1o0w=="], - "expo-router": ["expo-router@57.0.8", "", { "dependencies": { "@expo/log-box": "^57.0.1", "@expo/metro-runtime": "^57.0.7", "@expo/schema-utils": "^57.0.2", "@expo/ui": "^57.0.7", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^57.0.1", "expo-server": "^57.0.1", "expo-symbols": "^57.0.1", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.26.0", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^57.0.7", "expo-linking": "^57.0.4", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-xAyTnZl597G9/r17GOuyTy6VlhjYCVmgzgmP00bhZ9b+VstPl3tTrOOhSFagVpeln47nKp7x7vgkANNheCv4eQ=="], + "expo-router": ["expo-router@57.0.9", "", { "dependencies": { "@expo/log-box": "^57.0.2", "@expo/metro-runtime": "^57.0.8", "@expo/schema-utils": "^57.0.2", "@expo/ui": "^57.0.8", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^57.0.1", "expo-server": "^57.0.1", "expo-symbols": "^57.0.1", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.26.0", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^57.0.8", "expo-linking": "^57.0.4", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-jPBwHmKmBfzCLUuKgFf+I/UhFkxvS3J2WF5MQKcuupC8XzQPBAqdzbxmcPIDY3VIVabHw7WnhwzsROWA74t1LA=="], "expo-secure-store": ["expo-secure-store@57.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA=="], "expo-server": ["expo-server@57.0.1", "", {}, "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w=="], + "expo-sharing": ["expo-sharing@57.0.8", "", { "dependencies": { "@expo/config-plugins": "^57.0.6", "@expo/config-types": "^57.0.2", "@expo/plist": "^0.8.1" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZjiA09iJjzveachyBlbIijSH++fiacdvuenxb/gsdW21V3SUSxMQd+krPmafnhnMus5aBqpP0TXCBfBJN+0Bfg=="], + "expo-splash-screen": ["expo-splash-screen@57.0.4", "", { "dependencies": { "@expo/config-plugins": "~57.0.5", "@expo/image-utils": "^0.11.3", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-AspaOA5VZUl8yXb/BQORrRdOs0f7F/IgmvKNdCFaUDuz4X+j/4bPkkyssARFKwC5A5IQ3g7QS/Ve1XxDiDZ38Q=="], "expo-sqlite": ["expo-sqlite@57.0.1", "", { "dependencies": { "await-lock": "^2.2.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-I6KoUvfGIiROTKxr5D3H+jRIGA/iEvWEtqHK4XMukAA7tVTinz/YdS8zOz6/DdG6vgrNmvF7gyOcbhLinlfxzQ=="], @@ -2323,7 +2326,7 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - "hermes-compiler": ["hermes-compiler@250829098.0.14", "", {}, "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA=="], + "hermes-compiler": ["hermes-compiler@250829098.0.16", "", {}, "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw=="], "hermes-estree": ["hermes-estree@0.36.0", "", {}, "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w=="], @@ -3185,7 +3188,7 @@ "react-is": ["react-is@19.2.7", "", {}, "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A=="], - "react-native": ["react-native@0.86.0", "", { "dependencies": { "@react-native/assets-registry": "0.86.0", "@react-native/codegen": "0.86.0", "@react-native/community-cli-plugin": "0.86.0", "@react-native/gradle-plugin": "0.86.0", "@react-native/js-polyfills": "0.86.0", "@react-native/normalize-colors": "0.86.0", "@react-native/virtualized-lists": "0.86.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "250829098.0.14", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", "metro-source-map": "^0.84.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@react-native/jest-preset": "0.86.0", "@types/react": "^19.1.1", "react": "^19.2.3" }, "optionalPeers": ["@react-native/jest-preset", "@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w=="], + "react-native": ["react-native@0.86.2", "", { "dependencies": { "@react-native/assets-registry": "0.86.2", "@react-native/codegen": "0.86.2", "@react-native/community-cli-plugin": "0.86.2", "@react-native/gradle-plugin": "0.86.2", "@react-native/js-polyfills": "0.86.2", "@react-native/normalize-colors": "0.86.2", "@react-native/virtualized-lists": "0.86.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "250829098.0.16", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", "metro-source-map": "^0.84.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@react-native/jest-preset": "0.86.2", "@types/react": "^19.1.1", "react": "^19.2.3" }, "optionalPeers": ["@react-native/jest-preset", "@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A=="], "react-native-drawer-layout": ["react-native-drawer-layout@4.2.7", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-hhD+E0QmUPkP2Sj1MsUdrvU7GeOiHChPAFPtKahroTwlBGnpgJsUVSL0GWOy5cG3oCZfnu4Pb+gIzOa4ItGNuA=="], @@ -3863,7 +3866,7 @@ "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="], - "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], @@ -4017,7 +4020,7 @@ "@expo/package-manager/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@expo/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "@expo/prebuild-config/@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.0", "", {}, "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g=="], "@expo/ws-tunnel/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], @@ -4053,14 +4056,20 @@ "@radix-ui/react-tabs/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-collection": "1.1.12", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg=="], + "@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="], + "@react-native/babel-preset/react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], "@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + "@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.86.2", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.86.2", "@react-native/debugger-shell": "0.86.2", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-B7L0vKvg+IcEElT7Vpqh1xj5yJAqWUegjbP+bQRaorJMAYnv11GkliTnZV2AdTDfZQJWgOEx8i8LGkHkUg7bnA=="], + "@react-native/dev-middleware/chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="], "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], + "@react-native/metro-config/@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.0", "", {}, "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@shikijs/primitive/@shikijs/types": ["@shikijs/types@4.3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ=="], @@ -4097,6 +4106,8 @@ "@tiptap/markdown/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="], + "@types/react-test-renderer/@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + "addons-linter/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], "addons-linter/upath": ["upath@3.0.7", "", {}, "sha512-VjBBquch25nUGMuVBpOb2Cj3gc8Kb7lJBqbsXR/0anZ/5uJsL14Kpth9JKfnBsckxCfgIp6hPvcvvmZ97R9X7g=="], @@ -4219,10 +4230,14 @@ "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - "expo-router/@expo/metro-runtime": ["@expo/metro-runtime@57.0.7", "", { "dependencies": { "@expo/log-box": "^57.0.1", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-95UeoN/YsLellvskKsFGN9vKBwNc5k70ysO3skqfL3VusWlYIiYmPY+MpEWNz8A8W2CjLg9AKwZKAGrFj6znQg=="], + "expo-router/@expo/log-box": ["@expo/log-box@57.0.2", "", { "dependencies": { "@expo/dom-webview": "^57.0.1", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZsFyfIR7YCbQAdVLzuTUmMHofZC7ZS9ywYCJNPlLc78x59cI8GwXFEIVbRjjC0uJERpNtXx/tsNNnkhexXlzMw=="], + + "expo-router/@expo/metro-runtime": ["@expo/metro-runtime@57.0.8", "", { "dependencies": { "@expo/log-box": "^57.0.2", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-RrdehKXNtWpnm8nNs1QFhS0IauyMKR/nSQiry6dcUJs2T5EH8gYDMcMegUt9yI41K4nO3W8tr3O0xd/GZFObXQ=="], "expo-router/react-native-screens": ["react-native-screens@4.26.2", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A=="], + "expo-sharing/@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="], + "expo-updates/arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], "expo-updates/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -4343,8 +4358,6 @@ "pkijs/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], - "plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -4449,6 +4462,8 @@ "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], + "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], "yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], @@ -4519,14 +4534,10 @@ "@electron/osx-sign/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], - "@electron/osx-sign/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@electron/universal/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], - "@electron/universal/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], @@ -4569,10 +4580,20 @@ "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.12", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q=="], + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="], + "@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.86.2", "", {}, "sha512-KGS1aV5F6cIqpnoIUhLBXyVzy1oAj8jBFGau6vX4Vy0HXRJN7p+68RU7x6NuyraHvQcR14ccMGT5TkFuNjQ4gA=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-shell": ["@react-native/debugger-shell@0.86.2", "", { "dependencies": { "cross-spawn": "^7.0.6", "debug": "^4.4.0", "fb-dotslash": "0.5.8" } }, "sha512-/TaVJ2+gGajZPJGrFaObUQmHmlaxAlfmOPZicl6pNKDUjzSgFMpcLkdTOExvb+USYTVdGX1XwxXyvjQdUO2bvg=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], + "@react-native/dev-middleware/chrome-launcher/lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="], "@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], @@ -4745,14 +4766,24 @@ "expo-modules-autolinking/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "expo-sharing/@expo/config-plugins/@expo/require-utils": ["@expo/require-utils@57.0.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-e7xbg/9BTQcsZE/oErafZXtI7kh5IgfasLJ97J5sFSzX2cA74pDvdlhW1KHVSaDkQyQv6h1LSLhsY7dEeOk7hw=="], + + "expo-sharing/@expo/config-plugins/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "expo-sharing/@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "expo-updates/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "expo-updates/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "expo/babel-preset-expo/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "firefox-profile/xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "jest-util/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -4849,8 +4880,6 @@ "simple-plist/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], - "simple-plist/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "sitemap/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "svgo/css-tree/mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], @@ -4891,12 +4920,18 @@ "@iconify/tools/svgo/css-tree/mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + "@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + "@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "addons-linter/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -4977,8 +5012,14 @@ "expo-linking/expo-constants/@expo/env/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "expo-sharing/@expo/config-plugins/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "expo-sharing/@expo/config-plugins/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "expo-updates/glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="], + "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], @@ -5015,16 +5056,46 @@ "@expo/metro-config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "expo-linking/expo-constants/@expo/env/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "expo-sharing/@expo/config-plugins/glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "expo-updates/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "workbox-build/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "expo-sharing/@expo/config-plugins/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], } } diff --git a/tests/mobile-app-scope.test.ts b/tests/mobile-app-scope.test.ts index 893c2337..f28b04db 100644 --- a/tests/mobile-app-scope.test.ts +++ b/tests/mobile-app-scope.test.ts @@ -29,4 +29,12 @@ describe("mobile app scope", () => { expect(accountSecuritySource).not.toContain(removedCapability); } }); + + test("keeps version history reachable from an active note", () => { + expect(workspaceSource).toMatch( + /\{memo && !memo\.isDeleted \? \(\s*