From 5e258015096c44e969de76d7fc4e8cb861a6a42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cydy0615=E2=80=9D?= <“allenyuan410@gmail.com”> Date: Sat, 27 Jun 2026 22:28:01 +0800 Subject: [PATCH] feat: add InTEX iOS project and refresh agent guide --- .gitignore | 3 + AGENTS.md | 14 +- InTEX/InTEX.xcodeproj/project.pbxproj | 592 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ InTEX/InTEX/Assets.xcassets/Contents.json | 6 + InTEX/InTEX/ContentView.swift | 61 ++ InTEX/InTEX/InTEX.entitlements | 14 + InTEX/InTEX/InTEXApp.swift | 32 + InTEX/InTEX/Info.plist | 10 + InTEX/InTEX/Item.swift | 18 + InTEX/InTEXTests/InTEXTests.swift | 19 + InTEX/InTEXUITests/InTEXUITests.swift | 43 ++ .../InTEXUITestsLaunchTests.swift | 35 ++ 15 files changed, 899 insertions(+), 1 deletion(-) create mode 100644 InTEX/InTEX.xcodeproj/project.pbxproj create mode 100644 InTEX/InTEX.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 InTEX/InTEX/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 InTEX/InTEX/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 InTEX/InTEX/Assets.xcassets/Contents.json create mode 100644 InTEX/InTEX/ContentView.swift create mode 100644 InTEX/InTEX/InTEX.entitlements create mode 100644 InTEX/InTEX/InTEXApp.swift create mode 100644 InTEX/InTEX/Info.plist create mode 100644 InTEX/InTEX/Item.swift create mode 100644 InTEX/InTEXTests/InTEXTests.swift create mode 100644 InTEX/InTEXUITests/InTEXUITests.swift create mode 100644 InTEX/InTEXUITests/InTEXUITestsLaunchTests.swift diff --git a/.gitignore b/.gitignore index 6a78729..4eca64d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ api_performance_report.md !.vscode/extensions.json .idea .DS_Store +**/xcuserdata/ +*.xcuserstate +DerivedData/ *.suo *.ntvs* *.njsproj diff --git a/AGENTS.md b/AGENTS.md index 3e37a07..23c00f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ - 这是一个智能 Markdown 编辑器,前端负责编辑器 UI、上传导出、补全交互和设置状态,后端负责 LLM、OCR、文件转换和 TTS 接口。 - 前端技术栈:Vue 3 + Vite + Milkdown/Crepe + Pinia + Vue Router。 - 后端技术栈:FastAPI + Python + OpenAI-compatible LLM endpoint + Redis Streams。 +- 仓库内另有 `InTEX/` iOS 客户端工程,技术栈为 SwiftUI + SwiftData;当前仍是独立的 Xcode 初始工程,尚未接入 Web/Docker 后端。 - 项目版本:v0.2.0(自 b82c6d3 之后的全栈架构升级版本)。 ## 功能块系统(核心概念) @@ -62,6 +63,8 @@ - **字符串工具**:src/utils/string.ts(长度计算、编码检测) - **网页搜索上下文提取**:src/utils/webSearch.js(搜索结果 Markdown 构建与解析) - **测试配置和入口**:pytest.ini、backend/tests/run_tests.py +- **iOS 客户端工程**:InTEX/InTEX.xcodeproj(scheme: `InTEX`) +- **iOS 客户端入口**:InTEX/InTEX/InTEXApp.swift、InTEX/InTEX/ContentView.swift ## 稳定事实 @@ -86,7 +89,12 @@ - **上传单文件限制统一为 100MB**:前端校验和后端 OCR 风控上限都按 100MB 处理。 - **视频解析策略**:上传视频时,后端 `/v1/ocr` 接收 `media_type=video`,视频画面走 OCR 模型,音轨通过 ffmpeg 抽取后走 ASR 模型,最终合并为“视频画面 OCR + 视频音频 ASR”文本。 - **OCR 明确关闭思考**:backend/llm.py 的 OCR payload 显式下发 `options.think = False` 与 `temperature = 0`。 -- **TTS/ASR 当前真实实现**:backend/tts_asr.py 统一通过 `LLM_BASE_URL` + `LLM_API_KEY` 调用 OpenAI-compatible Speech API,默认模型为 `Qwen3-TTS-12Hz-1.7B-VoiceDesign-8bit` 与 `Qwen3-ASR-0.6B-8bit`。 +- **LLM/PRO/OCR 模型统一**:`LLM_MODEL`、`PRO_LLM_MODEL`、`VLM_MODEL` 的当前默认值和 Docker 运行值统一为 `Nex-N2-mini-mlx-OptiQ-8bit-MTP`。网页搜索模型仍可通过 `RISK_WEB_SEARCH_MODEL` 独立配置。 +- **OCR 完成状态不等于识别正确**:Redis 任务、审计入库和 SSE `done` 只能证明链路完成。2026-06-27 的清晰文本图片真实测试中,Nex OCR 仍可能返回未识别文本;用户已明确暂停继续修复 OCR 精度,不要把链路完成误报为 OCR 正常。 +- **审计 SQL 参数必须严格对齐**:`llm_call_audit` 当前包含 `queue_ms`、`run_ms`、`total_ms`,修改 INSERT 时必须保持列数、`%s` 占位符数和参数数一致;`backend/tests/test_audit_store.py` 覆盖此回归。 +- **TTS/ASR 当前真实实现**:backend/tts_asr.py 统一通过 `LLM_BASE_URL` + `LLM_API_KEY` 调用 OpenAI-compatible Speech API,默认模型为 `Qwen3-TTS-12Hz-1.7B-VoiceDesign-8bit` 与 `Qwen3-ASR-0.6B-8bit`。VoiceDesign 请求字段必须使用复数 `instructions`;空声音描述必须回退到 `TTS_DEFAULT_INSTRUCTIONS`,不能向上游发送空指令。 +- **六按钮收缩菜单**:MilkdownEditor.vue 的 upload/import/export/AI/template/clear 六个动作统一收进右下角 `.more-actions`。视口宽度 `>= 520px` 横向展开,窄屏纵向展开;再次点击主按钮、再次点击已打开的 export/template、点击 backdrop 或按 Escape 都必须完整收缩回右下角省略号。 +- **iOS 工程边界**:`InTEX/` 当前是 SwiftUI + SwiftData 示例骨架,包含 `InTEX`、`InTEXTests`、`InTEXUITests` 三个 target。不要声称它已接入编辑器或 Docker API;提交时禁止加入 `xcuserdata`、`*.xcuserstate` 和 DerivedData。 ## 常用命令 @@ -96,6 +104,8 @@ - 后端安装:pip install -r backend/requirements.txt - 后端启动:python backend/main.py - 可选启动方式:uvicorn backend.main:app --reload --port 8001 +- iOS 工程检查:xcodebuild -project InTEX/InTEX.xcodeproj -list +- iOS 模拟器构建:优先使用 XcodeBuildMCP;命令行回退可用 `xcodebuild -project InTEX/InTEX.xcodeproj -scheme InTEX -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO build` - 全量测试:pytest - 常用窄测试: - pytest backend/tests/test_main_endpoints.py -v @@ -123,6 +133,8 @@ 5. 执行 `docker compose up -d --no-build --force-recreate api worker frontend`,确保新容器来自已更新镜像。 6. 再次用 `docker compose exec -T ...` 验证容器内文件和行为,不能只看本地文件。 - Docker 持久化数据统一落在部署目录内的 `docker-data/`,包括 PostgreSQL、Redis 和任务共享临时目录。 +- Docker BuildKit 导出缓存统一落在 `docker-data/build-cache/{frontend,api,worker}`。`docker-compose.yml` 的 `cache_from/cache_to` 与 Dockerfile 的 apt/pip/npm cache mount 必须保留;首次构建填充缓存,后续相同源码构建应全部命中缓存。 +- `.dockerignore` 必须排除 `backend/models`、`backend/.env`、`docker-data`、`node_modules`、`.git` 等大目录或敏感文件。`backend/models` 是约 4.9GB 的旧本地模型目录,当前 API-based TTS/ASR 不应把它发送进构建上下文。 - 容器内访问宿主机模型服务时,不要继续使用 `localhost`;应改成 `host.docker.internal` 之类的容器可达地址。 - 当前 Docker 部署的 `backend/requirements.docker.txt` 已包含 OCR、转换、队列和基础 API 依赖;`backend/Dockerfile` 额外安装 `ffmpeg` 以支持视频拆音轨。 - **Worker 容器**:worker.py 作为独立服务运行,通过 Redis Streams 消费任务队列。修改 job_handlers.py 或 worker.py 后需要验证 worker 容器内的代码已更新,可通过 `docker compose exec -T worker sh -lc "python -c 'from backend.job_handlers import get_handler; print(get_handler(\"completion\").__name__)'"` 验证。 diff --git a/InTEX/InTEX.xcodeproj/project.pbxproj b/InTEX/InTEX.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b91ff4b --- /dev/null +++ b/InTEX/InTEX.xcodeproj/project.pbxproj @@ -0,0 +1,592 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXContainerItemProxy section */ + A75D91532FF0146B00EE6196 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A75D91392FF0146A00EE6196 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A75D91402FF0146A00EE6196; + remoteInfo = InTEX; + }; + A75D915D2FF0146B00EE6196 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A75D91392FF0146A00EE6196 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A75D91402FF0146A00EE6196; + remoteInfo = InTEX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + A75D91412FF0146A00EE6196 /* InTEX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InTEX.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A75D91522FF0146B00EE6196 /* InTEXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InTEXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A75D915C2FF0146B00EE6196 /* InTEXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InTEXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + A75D91642FF0146B00EE6196 /* Exceptions for "InTEX" folder in "InTEX" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = A75D91402FF0146A00EE6196 /* InTEX */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + A75D91432FF0146A00EE6196 /* InTEX */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + A75D91642FF0146B00EE6196 /* Exceptions for "InTEX" folder in "InTEX" target */, + ); + path = InTEX; + sourceTree = ""; + }; + A75D91552FF0146B00EE6196 /* InTEXTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = InTEXTests; + sourceTree = ""; + }; + A75D915F2FF0146B00EE6196 /* InTEXUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = InTEXUITests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + A75D913E2FF0146A00EE6196 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D914F2FF0146B00EE6196 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D91592FF0146B00EE6196 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A75D91382FF0146A00EE6196 = { + isa = PBXGroup; + children = ( + A75D91432FF0146A00EE6196 /* InTEX */, + A75D91552FF0146B00EE6196 /* InTEXTests */, + A75D915F2FF0146B00EE6196 /* InTEXUITests */, + A75D91422FF0146A00EE6196 /* Products */, + ); + sourceTree = ""; + }; + A75D91422FF0146A00EE6196 /* Products */ = { + isa = PBXGroup; + children = ( + A75D91412FF0146A00EE6196 /* InTEX.app */, + A75D91522FF0146B00EE6196 /* InTEXTests.xctest */, + A75D915C2FF0146B00EE6196 /* InTEXUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A75D91402FF0146A00EE6196 /* InTEX */ = { + isa = PBXNativeTarget; + buildConfigurationList = A75D91652FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEX" */; + buildPhases = ( + A75D913D2FF0146A00EE6196 /* Sources */, + A75D913E2FF0146A00EE6196 /* Frameworks */, + A75D913F2FF0146A00EE6196 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + A75D91432FF0146A00EE6196 /* InTEX */, + ); + name = InTEX; + packageProductDependencies = ( + ); + productName = InTEX; + productReference = A75D91412FF0146A00EE6196 /* InTEX.app */; + productType = "com.apple.product-type.application"; + }; + A75D91512FF0146B00EE6196 /* InTEXTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A75D916A2FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEXTests" */; + buildPhases = ( + A75D914E2FF0146B00EE6196 /* Sources */, + A75D914F2FF0146B00EE6196 /* Frameworks */, + A75D91502FF0146B00EE6196 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A75D91542FF0146B00EE6196 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + A75D91552FF0146B00EE6196 /* InTEXTests */, + ); + name = InTEXTests; + packageProductDependencies = ( + ); + productName = InTEXTests; + productReference = A75D91522FF0146B00EE6196 /* InTEXTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A75D915B2FF0146B00EE6196 /* InTEXUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A75D916D2FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEXUITests" */; + buildPhases = ( + A75D91582FF0146B00EE6196 /* Sources */, + A75D91592FF0146B00EE6196 /* Frameworks */, + A75D915A2FF0146B00EE6196 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A75D915E2FF0146B00EE6196 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + A75D915F2FF0146B00EE6196 /* InTEXUITests */, + ); + name = InTEXUITests; + packageProductDependencies = ( + ); + productName = InTEXUITests; + productReference = A75D915C2FF0146B00EE6196 /* InTEXUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A75D91392FF0146A00EE6196 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2650; + LastUpgradeCheck = 2650; + TargetAttributes = { + A75D91402FF0146A00EE6196 = { + CreatedOnToolsVersion = 26.5; + }; + A75D91512FF0146B00EE6196 = { + CreatedOnToolsVersion = 26.5; + TestTargetID = A75D91402FF0146A00EE6196; + }; + A75D915B2FF0146B00EE6196 = { + CreatedOnToolsVersion = 26.5; + TestTargetID = A75D91402FF0146A00EE6196; + }; + }; + }; + buildConfigurationList = A75D913C2FF0146A00EE6196 /* Build configuration list for PBXProject "InTEX" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A75D91382FF0146A00EE6196; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = A75D91422FF0146A00EE6196 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A75D91402FF0146A00EE6196 /* InTEX */, + A75D91512FF0146B00EE6196 /* InTEXTests */, + A75D915B2FF0146B00EE6196 /* InTEXUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A75D913F2FF0146A00EE6196 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D91502FF0146B00EE6196 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D915A2FF0146B00EE6196 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A75D913D2FF0146A00EE6196 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D914E2FF0146B00EE6196 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A75D91582FF0146B00EE6196 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + A75D91542FF0146B00EE6196 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A75D91402FF0146A00EE6196 /* InTEX */; + targetProxy = A75D91532FF0146B00EE6196 /* PBXContainerItemProxy */; + }; + A75D915E2FF0146B00EE6196 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A75D91402FF0146A00EE6196 /* InTEX */; + targetProxy = A75D915D2FF0146B00EE6196 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + A75D91662FF0146B00EE6196 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = InTEX/InTEX.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = InTEX/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEX; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A75D91672FF0146B00EE6196 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = InTEX/InTEX.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = InTEX/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEX; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + A75D91682FF0146B00EE6196 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A75D91692FF0146B00EE6196 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A75D916B2FF0146B00EE6196 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/InTEX.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/InTEX"; + }; + name = Debug; + }; + A75D916C2FF0146B00EE6196 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/InTEX.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/InTEX"; + }; + name = Release; + }; + A75D916E2FF0146B00EE6196 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEXUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = InTEX; + }; + name = Debug; + }; + A75D916F2FF0146B00EE6196 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = imageteach.InTEXUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = InTEX; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A75D913C2FF0146A00EE6196 /* Build configuration list for PBXProject "InTEX" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A75D91682FF0146B00EE6196 /* Debug */, + A75D91692FF0146B00EE6196 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A75D91652FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEX" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A75D91662FF0146B00EE6196 /* Debug */, + A75D91672FF0146B00EE6196 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A75D916A2FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEXTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A75D916B2FF0146B00EE6196 /* Debug */, + A75D916C2FF0146B00EE6196 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A75D916D2FF0146B00EE6196 /* Build configuration list for PBXNativeTarget "InTEXUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A75D916E2FF0146B00EE6196 /* Debug */, + A75D916F2FF0146B00EE6196 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A75D91392FF0146A00EE6196 /* Project object */; +} diff --git a/InTEX/InTEX.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/InTEX/InTEX.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/InTEX/InTEX.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/InTEX/InTEX/Assets.xcassets/AccentColor.colorset/Contents.json b/InTEX/InTEX/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/InTEX/InTEX/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InTEX/InTEX/Assets.xcassets/AppIcon.appiconset/Contents.json b/InTEX/InTEX/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/InTEX/InTEX/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InTEX/InTEX/Assets.xcassets/Contents.json b/InTEX/InTEX/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/InTEX/InTEX/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InTEX/InTEX/ContentView.swift b/InTEX/InTEX/ContentView.swift new file mode 100644 index 0000000..5d27903 --- /dev/null +++ b/InTEX/InTEX/ContentView.swift @@ -0,0 +1,61 @@ +// +// ContentView.swift +// InTEX +// +// Created by allenyuan on 2026/6/27. +// + +import SwiftUI +import SwiftData + +struct ContentView: View { + @Environment(\.modelContext) private var modelContext + @Query private var items: [Item] + + var body: some View { + NavigationSplitView { + List { + ForEach(items) { item in + NavigationLink { + Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") + } label: { + Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) + } + } + .onDelete(perform: deleteItems) + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + EditButton() + } + ToolbarItem { + Button(action: addItem) { + Label("Add Item", systemImage: "plus") + } + } + } + } detail: { + Text("Select an item") + } + } + + private func addItem() { + withAnimation { + let newItem = Item(timestamp: Date()) + modelContext.insert(newItem) + } + } + + private func deleteItems(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(items[index]) + } + } + } +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} diff --git a/InTEX/InTEX/InTEX.entitlements b/InTEX/InTEX/InTEX.entitlements new file mode 100644 index 0000000..9e0940e --- /dev/null +++ b/InTEX/InTEX/InTEX.entitlements @@ -0,0 +1,14 @@ + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + com.apple.developer.icloud-services + + CloudKit + + + diff --git a/InTEX/InTEX/InTEXApp.swift b/InTEX/InTEX/InTEXApp.swift new file mode 100644 index 0000000..7675ead --- /dev/null +++ b/InTEX/InTEX/InTEXApp.swift @@ -0,0 +1,32 @@ +// +// InTEXApp.swift +// InTEX +// +// Created by allenyuan on 2026/6/27. +// + +import SwiftUI +import SwiftData + +@main +struct InTEXApp: App { + var sharedModelContainer: ModelContainer = { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + }() + + var body: some Scene { + WindowGroup { + ContentView() + } + .modelContainer(sharedModelContainer) + } +} diff --git a/InTEX/InTEX/Info.plist b/InTEX/InTEX/Info.plist new file mode 100644 index 0000000..ca9a074 --- /dev/null +++ b/InTEX/InTEX/Info.plist @@ -0,0 +1,10 @@ + + + + + UIBackgroundModes + + remote-notification + + + diff --git a/InTEX/InTEX/Item.swift b/InTEX/InTEX/Item.swift new file mode 100644 index 0000000..8fc9679 --- /dev/null +++ b/InTEX/InTEX/Item.swift @@ -0,0 +1,18 @@ +// +// Item.swift +// InTEX +// +// Created by allenyuan on 2026/6/27. +// + +import Foundation +import SwiftData + +@Model +final class Item { + var timestamp: Date + + init(timestamp: Date) { + self.timestamp = timestamp + } +} diff --git a/InTEX/InTEXTests/InTEXTests.swift b/InTEX/InTEXTests/InTEXTests.swift new file mode 100644 index 0000000..dc46caf --- /dev/null +++ b/InTEX/InTEXTests/InTEXTests.swift @@ -0,0 +1,19 @@ +// +// InTEXTests.swift +// InTEXTests +// +// Created by allenyuan on 2026/6/27. +// + +import Testing +@testable import InTEX + +struct InTEXTests { + + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + // Swift Testing Documentation + // https://developer.apple.com/documentation/testing + } + +} diff --git a/InTEX/InTEXUITests/InTEXUITests.swift b/InTEX/InTEXUITests/InTEXUITests.swift new file mode 100644 index 0000000..f570f00 --- /dev/null +++ b/InTEX/InTEXUITests/InTEXUITests.swift @@ -0,0 +1,43 @@ +// +// InTEXUITests.swift +// InTEXUITests +// +// Created by allenyuan on 2026/6/27. +// + +import XCTest + +final class InTEXUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + @MainActor + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + // XCUIAutomation Documentation + // https://developer.apple.com/documentation/xcuiautomation + } + + @MainActor + func testLaunchPerformance() throws { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } +} diff --git a/InTEX/InTEXUITests/InTEXUITestsLaunchTests.swift b/InTEX/InTEXUITests/InTEXUITestsLaunchTests.swift new file mode 100644 index 0000000..9dc599c --- /dev/null +++ b/InTEX/InTEXUITests/InTEXUITestsLaunchTests.swift @@ -0,0 +1,35 @@ +// +// InTEXUITestsLaunchTests.swift +// InTEXUITests +// +// Created by allenyuan on 2026/6/27. +// + +import XCTest + +final class InTEXUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + // XCUIAutomation Documentation + // https://developer.apple.com/documentation/xcuiautomation + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +}