diff --git a/docs/arxiv-discovery-native-transition.md b/docs/arxiv-discovery-native-transition.md new file mode 100644 index 0000000..f817105 --- /dev/null +++ b/docs/arxiv-discovery-native-transition.md @@ -0,0 +1,23 @@ +# Arxiv Discovery native transition + +Arxiv Discovery moved from a Python CLI and local Flask interface to a standalone macOS application on 2026-07-15. The source repository is `Gnaroshi/Arxiv-newest-paper-crawler`. The approved native capture baseline is commit `1f87f235e2bef334980c19e019c016bb2a60ef0c`, and the installed delivery head is `48de418`. + +## Product boundary + +- SwiftUI owns recent-paper discovery, subject filters, search, detail review, saved metadata, explicit PDF actions, optional Korean translation, and preferences. +- The app reads public arXiv metadata directly and stores local state under Application Support. +- The Gemini API key stays in Keychain. Translation sends a selected public title and abstract only after an explicit action. +- The Python CLIs remain compatibility paths. The schema-v1 safe discovery/export provider is retained for scripts, while legacy `serve` and `all` entry points open the native app instead of starting a local web server. +- Studio integration is read-only: availability, launch, safe counts, and freshness may be exposed, while abstracts, credentials, translation, downloads, and saved-paper ownership remain inside Arxiv Discovery. + +## Distribution and verification + +The native delivery uses bundle identifier `dev.gnaroshi.ArxivDiscovery`, version `0.3.0`, and build `26`. A Developer ID signed build was installed at `/Applications/Arxiv Discovery.app`, verified with `codesign`, indexed by Spotlight and LaunchServices, launched from the installed bundle, and checked against live public arXiv metadata. The repository also verifies Python compatibility and provider tests, lint, Swift debug and release builds, core parsing and repository checks, manifest identity, entitlements, provenance, and the approved app-icon hash. + +## Website evidence + +The public project page uses the owner-approved 1946 × 1480 native window capture from commit `1f87f235e2be`. Showcase mode renders three deterministic paper candidates, includes a visible disclosure, and disables network requests, translation, and downloads. The previous Flask screenshots are retired historical records and are not published as evidence for the native product. + +## Compatibility and rollback + +Existing public project routes and the repository link remain unchanged. Legacy crawler JSON can be imported into the app, and the Python `process` command remains available. If the native source baseline must be rolled back, revert the application repository first, then restore the website fact and only the approved media whose source commit exactly matches that restored baseline. diff --git a/docs/project-screenshot-guide.md b/docs/project-screenshot-guide.md index 37f7528..d436610 100644 --- a/docs/project-screenshot-guide.md +++ b/docs/project-screenshot-guide.md @@ -40,9 +40,11 @@ The application pages remain evidence-gated. Captures are stored outside `public | --- | --- | --- | | `project-gnaroshi-studio-review` | Actual Managed Apps overview with external apps unavailable or needing setup | Reviewable; no private workspace records are visible | | `project-paperflow-review` | Actual PaperFlow organization screen | Reviewable; shows the dry-run and explicit apply boundary without library records | -| `project-arxiv-discovery-review` | Actual Flask UI populated by a no-download query of public arXiv metadata | Reviewable; current public paper metadata only | +| `project-arxiv-discovery-review` | Native SwiftUI window with three deterministic paper candidates and all network actions disabled | Owner-approved on 2026-07-15; publish only from the matching source manifest and commit | | `project-tr-gpu-monitor-review` | Actual SSH prerequisite screen | Rejected for production because it is not a sanitized monitoring summary | | `project-runshelf-review` | Actual app rendering checked-in sample records | Rejected for production because sample metrics are not research evidence | | `project-contentdeck-review` | Actual Electron player in its empty prerequisite state | Rejected for production because it does not show supported playback, subtitles, or a loop | No capture contains a private repository URL, branch, commit, token, credential, personal paper record, or private host alias. Production exports must be regenerated only from owner-approved candidates, and the three rejected states require replacement evidence rather than cosmetic editing. + +Arxiv Discovery moved from the Flask interface to a native SwiftUI app on 2026-07-15. The owner approved the 1946 × 1480 native capture tied to source commit `1f87f235e2bef334980c19e019c016bb2a60ef0c`. It uses deterministic metadata, displays its showcase disclosure, and disables network, translation, and download actions. Do not reuse the retired Flask captures as native evidence; importing a later manifest still requires a fresh owner review. diff --git a/docs/project-showcase-staging.md b/docs/project-showcase-staging.md index e8e6c38..73c53bc 100644 --- a/docs/project-showcase-staging.md +++ b/docs/project-showcase-staging.md @@ -7,22 +7,24 @@ Application screenshots are captured in each owning repository with an explicit 3. Run `npm run showcases:import`. 4. Start development and review `/dev/project-showcases/` in light/dark and desktop/mobile crops. 5. Record owner-approved captures in `src/data/approvedProjectShowcases.ts` with source commit, localized alt/caption, demo disclosure, and privacy review. -6. Run `npm run showcases:publish` to create responsive AVIF/WebP files without cropping the source. +6. Run `npm run showcases:publish` to create responsive AVIF/WebP files without cropping the source. When only one approved application source is locally available, scope regeneration with `SHOWCASE_APPLICATION_ID= npm run showcases:publish`; the command fails if that application has no approved record. 7. Run `npm run media:check` and `npm run check:project-readiness`. Raw and normalized captures remain under ignored `artifacts/project-showcases/`. Manifests reject absolute paths, secret-like values, missing source commits, undisclosed fixtures, and dimension mismatches. ## Approved set -The owner approved all 24 captures prepared on 2026-07-13: four appearance/workflow captures each for Gnaroshi Studio, PaperFlow, Arxiv Discovery, RunShelf, TR GPU Monitor, and ContentDeck. The website publishes dark workflow captures as scenario evidence and retains each light capture as reviewed appearance evidence. +The owner approved 24 captures prepared on 2026-07-13: four appearance/workflow captures each for Gnaroshi Studio, PaperFlow, the retired Flask Arxiv Discovery interface, RunShelf, TR GPU Monitor, and ContentDeck. On 2026-07-15, the four Flask captures were retired from the current register and replaced by one owner-approved native SwiftUI capture. The current register therefore contains 21 screenshots: 20 retained captures and the native Arxiv Discovery capture. | Application | Source commit | Primary public capture | | --- | --- | --- | | Gnaroshi Studio | `e6115c90ab86` | `gnaroshi-studio-managed-apps` | | PaperFlow | `7c7bd013d171` | `paperflow-plan-review` | -| Arxiv Discovery | `e48da6e1434a` | `arxiv-discovery-discovery-list` | +| Arxiv Discovery | `1f87f235e2be` | `arxiv-discovery-discovery-list` | | RunShelf | `7657067cfeae` | `runshelf-run-list` | | TR GPU Monitor | `3a2fa8173f3b` | `tr-gpu-monitor-host-overview` | | ContentDeck | `5c6e2f94d1e` | `contentdeck-active-loop` | Every approved capture uses deterministic demo data and includes a visible localized disclosure wherever it appears. Private repository URLs, local filesystem paths, host credentials, real library records, raw experiment paths, and unpublished research data are excluded. + +The current Arxiv Discovery record uses a 2x native window capture from source commit `1f87f235e2be`. Its showcase mode contains three deterministic candidates, visibly discloses that mode, and disables network requests, translation, and downloads. The previous Flask files from `e48da6e1434a` are historical only and must not be restored or relabeled as native evidence. diff --git a/docs/projects-content-model.md b/docs/projects-content-model.md index ecbe71e..db60d07 100644 --- a/docs/projects-content-model.md +++ b/docs/projects-content-model.md @@ -17,6 +17,8 @@ The three project kinds are `research`, `application`, and `infrastructure`. App The index keeps selected work separate from applications. Application cards use the shared `applicationGroup` fact to distinguish research workflow, system utility, and learning tool roles without duplicating that classification in localized prose. +Application evidence is a discriminated pair. A current owner-approved capture uses a non-null `primaryShowcaseId` and a null `textOnlyExemption`. When an interface changes before a new capture is approved, the record instead uses a null `primaryShowcaseId` and an internal non-empty `textOnlyExemption`; the page omits stale media and renders the verified text workflow. The exemption is validation context and is never public copy. + ## Localized stories `src/data/projectStories.ts` owns prose only. English and Korean each provide `cardSummary`, `heroSummary`, `overview`, audience, primary use, key features, privacy, current state, limitations, milestones, scenario copy, and kind-specific detail copy. diff --git a/docs/projects-tech-stack.md b/docs/projects-tech-stack.md index 3cf7e8b..5af7fdd 100644 --- a/docs/projects-tech-stack.md +++ b/docs/projects-tech-stack.md @@ -10,7 +10,7 @@ | gnaroshi.dev | Astro, TypeScript, GitHub Actions, Playwright | `package.json` and workflow files | | Gnaroshi Studio | TypeScript, React, Rust, Tauri | workspace package manifests and Cargo manifest | | PaperFlow | Python, Swift, SwiftUI, Zotero Local API | `pyproject.toml`, Swift package/source, repository code | -| Arxiv Discovery | Python, Flask, Jinja | `pyproject.toml`, Flask application, templates | +| Arxiv Discovery | Swift, SwiftUI, Python | `Package.swift`, Swift package/source, `pyproject.toml` | | RunShelf | Python, Swift, SwiftUI | `pyproject.toml`, Swift package/source | | TR GPU Monitor | Swift, SwiftUI, SQLite | Xcode source and SQLite persistence implementation | | ContentDeck | TypeScript, React, Electron, Vite, Fastify | `package.json` | diff --git a/playwright.config.ts b/playwright.config.ts index a48d2f3..c9cc5bc 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,6 @@ import { defineConfig, devices } from "@playwright/test"; -const port = 4324; +const port = Number(process.env.PLAYWRIGHT_PORT ?? 4324); export default defineConfig({ testDir: "./tests/e2e", diff --git a/public/media/approved/arxiv-discovery-candidate-detail-1200.avif b/public/media/approved/arxiv-discovery-candidate-detail-1200.avif deleted file mode 100644 index 205855a..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-1200.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-1200.webp b/public/media/approved/arxiv-discovery-candidate-detail-1200.webp deleted file mode 100644 index 708a81a..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-1200.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-1600.avif b/public/media/approved/arxiv-discovery-candidate-detail-1600.avif deleted file mode 100644 index c714619..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-1600.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-1600.webp b/public/media/approved/arxiv-discovery-candidate-detail-1600.webp deleted file mode 100644 index 83ed1f6..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-1600.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-640.avif b/public/media/approved/arxiv-discovery-candidate-detail-640.avif deleted file mode 100644 index bf06478..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-640.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-640.webp b/public/media/approved/arxiv-discovery-candidate-detail-640.webp deleted file mode 100644 index 8253847..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-640.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-960.avif b/public/media/approved/arxiv-discovery-candidate-detail-960.avif deleted file mode 100644 index 279822a..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-960.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-candidate-detail-960.webp b/public/media/approved/arxiv-discovery-candidate-detail-960.webp deleted file mode 100644 index 8732f61..0000000 Binary files a/public/media/approved/arxiv-discovery-candidate-detail-960.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-1200.avif b/public/media/approved/arxiv-discovery-discovery-list-1200.avif index 1091e7e..54e14fd 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-1200.avif and b/public/media/approved/arxiv-discovery-discovery-list-1200.avif differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-1200.webp b/public/media/approved/arxiv-discovery-discovery-list-1200.webp index 935a849..0b34f35 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-1200.webp and b/public/media/approved/arxiv-discovery-discovery-list-1200.webp differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-1600.avif b/public/media/approved/arxiv-discovery-discovery-list-1600.avif index 1e8b604..e9a8e5c 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-1600.avif and b/public/media/approved/arxiv-discovery-discovery-list-1600.avif differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-1600.webp b/public/media/approved/arxiv-discovery-discovery-list-1600.webp index 86c074b..f783b52 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-1600.webp and b/public/media/approved/arxiv-discovery-discovery-list-1600.webp differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-640.avif b/public/media/approved/arxiv-discovery-discovery-list-640.avif index 4fa2c5c..2b65eb5 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-640.avif and b/public/media/approved/arxiv-discovery-discovery-list-640.avif differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-640.webp b/public/media/approved/arxiv-discovery-discovery-list-640.webp index 2d0bf88..e6bcaa4 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-640.webp and b/public/media/approved/arxiv-discovery-discovery-list-640.webp differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-960.avif b/public/media/approved/arxiv-discovery-discovery-list-960.avif index 8bb0791..3b84039 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-960.avif and b/public/media/approved/arxiv-discovery-discovery-list-960.avif differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-960.webp b/public/media/approved/arxiv-discovery-discovery-list-960.webp index 7a99d49..693f7e8 100644 Binary files a/public/media/approved/arxiv-discovery-discovery-list-960.webp and b/public/media/approved/arxiv-discovery-discovery-list-960.webp differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-1200.avif b/public/media/approved/arxiv-discovery-discovery-list-light-1200.avif deleted file mode 100644 index 2bf4825..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-1200.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-1200.webp b/public/media/approved/arxiv-discovery-discovery-list-light-1200.webp deleted file mode 100644 index e6672d3..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-1200.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-1600.avif b/public/media/approved/arxiv-discovery-discovery-list-light-1600.avif deleted file mode 100644 index d9c7b02..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-1600.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-1600.webp b/public/media/approved/arxiv-discovery-discovery-list-light-1600.webp deleted file mode 100644 index f37abd2..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-1600.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-640.avif b/public/media/approved/arxiv-discovery-discovery-list-light-640.avif deleted file mode 100644 index 09f0682..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-640.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-640.webp b/public/media/approved/arxiv-discovery-discovery-list-light-640.webp deleted file mode 100644 index 93e7724..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-640.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-960.avif b/public/media/approved/arxiv-discovery-discovery-list-light-960.avif deleted file mode 100644 index 8bf9a08..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-960.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-discovery-list-light-960.webp b/public/media/approved/arxiv-discovery-discovery-list-light-960.webp deleted file mode 100644 index 04a3ab2..0000000 Binary files a/public/media/approved/arxiv-discovery-discovery-list-light-960.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-1200.avif b/public/media/approved/arxiv-discovery-handoff-preview-1200.avif deleted file mode 100644 index 7d2ced3..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-1200.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-1200.webp b/public/media/approved/arxiv-discovery-handoff-preview-1200.webp deleted file mode 100644 index f3e4b6c..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-1200.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-1600.avif b/public/media/approved/arxiv-discovery-handoff-preview-1600.avif deleted file mode 100644 index fb63f41..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-1600.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-1600.webp b/public/media/approved/arxiv-discovery-handoff-preview-1600.webp deleted file mode 100644 index 503d433..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-1600.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-640.avif b/public/media/approved/arxiv-discovery-handoff-preview-640.avif deleted file mode 100644 index de16314..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-640.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-640.webp b/public/media/approved/arxiv-discovery-handoff-preview-640.webp deleted file mode 100644 index 715eae2..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-640.webp and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-960.avif b/public/media/approved/arxiv-discovery-handoff-preview-960.avif deleted file mode 100644 index c4997a4..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-960.avif and /dev/null differ diff --git a/public/media/approved/arxiv-discovery-handoff-preview-960.webp b/public/media/approved/arxiv-discovery-handoff-preview-960.webp deleted file mode 100644 index b6e5bd9..0000000 Binary files a/public/media/approved/arxiv-discovery-handoff-preview-960.webp and /dev/null differ diff --git a/scripts/check-project-readiness.mjs b/scripts/check-project-readiness.mjs index ebfbb47..a634f3e 100644 --- a/scripts/check-project-readiness.mjs +++ b/scripts/check-project-readiness.mjs @@ -31,7 +31,7 @@ for (const project of projectFacts) { if (!integrationStatuses.has(project.studioIntegrationStatus)) failures.push(`${project.id}: invalid Studio integration status`); if (!/^[0-9a-f]{40}$/.test(project.sourceCommit)) failures.push(`${project.id}: missing full source commit`); if (!project.sourceRepository) failures.push(`${project.id}: source provenance missing`); - const primary = getApprovedProjectShowcase(project.primaryShowcaseId); + const primary = project.primaryShowcaseId ? getApprovedProjectShowcase(project.primaryShowcaseId) : undefined; if (!primary && !project.textOnlyExemption) failures.push(`${project.id}: primary approved screenshot missing`); if (primary && (primary.applicationId !== project.id || primary.sourceApplicationCommit !== project.sourceCommit)) failures.push(`${project.id}: primary screenshot provenance mismatch`); if (project.studioIntegrationStatus === project.productStatus) failures.push(`${project.id}: product and Studio integration status are conflated`); diff --git a/scripts/publish-approved-project-showcases.mjs b/scripts/publish-approved-project-showcases.mjs index a99d1b4..9fd1683 100644 --- a/scripts/publish-approved-project-showcases.mjs +++ b/scripts/publish-approved-project-showcases.mjs @@ -17,9 +17,16 @@ if (!existsSync(configPath)) { const config = JSON.parse(await readFile(configPath, "utf8")); const sourcePaths = new Map(config.applications?.map((item) => [item.id, item.sourcePath]) ?? []); +const requestedApplicationId = process.env.SHOWCASE_APPLICATION_ID; +const selectedShowcases = requestedApplicationId + ? approvedProjectShowcases.filter((asset) => asset.applicationId === requestedApplicationId) + : approvedProjectShowcases; +if (requestedApplicationId && selectedShowcases.length === 0) { + fail(`No owner-approved screenshots registered for ${requestedApplicationId}`); +} await mkdir(outputRoot, { recursive: true }); -for (const asset of approvedProjectShowcases) { +for (const asset of selectedShowcases) { const configuredPath = sourcePaths.get(asset.applicationId); if (!configuredPath || !isAbsolute(configuredPath)) fail(`${asset.applicationId}: sourcePath must be absolute`); const sourceRoot = resolve(configuredPath); @@ -46,4 +53,4 @@ for (const asset of approvedProjectShowcases) { } } -console.log(`[showcases:publish] published ${approvedProjectShowcases.length} owner-approved screenshots`); +console.log(`[showcases:publish] published ${selectedShowcases.length} owner-approved screenshots`); diff --git a/src/components/projects/ApplicationProjectDetail.astro b/src/components/projects/ApplicationProjectDetail.astro index d1ad184..7c014b6 100644 --- a/src/components/projects/ApplicationProjectDetail.astro +++ b/src/components/projects/ApplicationProjectDetail.astro @@ -10,16 +10,16 @@ import ProjectDetailHeader from "./ProjectDetailHeader.astro"; import ProjectPrimaryMedia from "./ProjectPrimaryMedia.astro"; import ProjectScenario from "./ProjectScenario.astro"; import ProjectStoryFacts from "./ProjectStoryFacts.astro"; -interface Props { project: ApplicationProjectFacts; story: ProjectStory; locale: Locale; media: ApprovedProjectShowcase; hasPublicRepository: boolean } +interface Props { project: ApplicationProjectFacts; story: ProjectStory; locale: Locale; media?: ApprovedProjectShowcase; hasPublicRepository: boolean } const { project, story, locale, media, hasPublicRepository } = Astro.props; const labels = getProjectUi(locale); ---
- + {media && }

{labels.overview}

{story.overview}

- +

{locale === "ko" ? "기술 사실" : "Technical facts"}

{labels.source}
{hasPublicRepository ? project.sourceRepository : labels.privateSource}
{labels.sourceCommit}
{project.sourceCommit.slice(0,12)}
diff --git a/src/components/projects/FeaturedApplicationCard.astro b/src/components/projects/FeaturedApplicationCard.astro index c438b8b..a91c047 100644 --- a/src/components/projects/FeaturedApplicationCard.astro +++ b/src/components/projects/FeaturedApplicationCard.astro @@ -12,7 +12,7 @@ import TechStackStrip from "./TechStackStrip.astro"; interface Props { project: ApplicationProjectFacts; story: ProjectStory; - media: ApprovedProjectShowcase; + media?: ApprovedProjectShowcase; locale: Locale; href: string; detailsLabel: string; @@ -24,14 +24,14 @@ const { project, story, media, locale, href, detailsLabel, stackLabel, lead = fa ---
- + }