diff --git a/.github/workflows/build-fork-macos.yml b/.github/workflows/build-fork-macos.yml index 1b0d57105..b926348d6 100644 --- a/.github/workflows/build-fork-macos.yml +++ b/.github/workflows/build-fork-macos.yml @@ -1,4 +1,4 @@ -name: Build OpenScreen Subscription for macOS +name: Build PurplePrint Studio for macOS on: workflow_dispatch: @@ -98,8 +98,8 @@ jobs: run: | set -euo pipefail VERSION="$(node -p 'require("./package.json").version')" - APP="$(find "release/${VERSION}" -maxdepth 3 -name 'OpenScreen Subscription.app' -type d -print -quit)" - [ -n "$APP" ] || { echo "::error::OpenScreen Subscription.app was not packaged"; exit 1; } + APP="$(find "release/${VERSION}" -maxdepth 3 -name 'PurplePrint Studio.app' -type d -print -quit)" + [ -n "$APP" ] || { echo "::error::PurplePrint Studio.app was not packaged"; exit 1; } echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "path=${APP}" >> "$GITHUB_OUTPUT" @@ -114,7 +114,7 @@ jobs: run: | set -euo pipefail APP="${{ steps.app.outputs.path }}" - BIN="${APP}/Contents/MacOS/OpenScreen Subscription" + BIN="${APP}/Contents/MacOS/PurplePrint Studio" RESOURCES="${APP}/Contents/Resources" NATIVE="${RESOURCES}/electron/native/bin/darwin-${{ matrix.arch }}" @@ -147,9 +147,9 @@ jobs: - name: Smoke-test packaged CLI boot run: | set -euo pipefail - BIN="${{ steps.app.outputs.path }}/Contents/MacOS/OpenScreen Subscription" + BIN="${{ steps.app.outputs.path }}/Contents/MacOS/PurplePrint Studio" "$BIN" help > cli-help.txt - grep -q "OpenScreen CLI" cli-help.txt + grep -Fq "PurplePrint Studio CLI (openscreen command)" cli-help.txt - name: Create DMG id: dmg @@ -160,7 +160,7 @@ jobs: arm64) LABEL="Apple-Silicon" ;; x64) LABEL="Intel" ;; esac - OUT="release/${VERSION}/OpenScreen-Subscription-${VERSION}-macOS-${LABEL}.dmg" + OUT="release/${VERSION}/PurplePrint-Studio-${VERSION}-macOS-${LABEL}.dmg" STAGING="release/${VERSION}/dmg-${{ matrix.arch }}" rm -rf "$STAGING" "$OUT" mkdir -p "$STAGING" @@ -168,7 +168,7 @@ jobs: ln -s /Applications "$STAGING/Applications" hdiutil create \ -srcfolder "$STAGING" \ - -volname "OpenScreen Subscription" \ + -volname "PurplePrint Studio" \ -fs HFS+ \ -format UDBZ \ "$OUT" @@ -185,7 +185,7 @@ jobs: } trap cleanup EXIT hdiutil attach -readonly -nobrowse -mountpoint "$MOUNT" "${{ steps.dmg.outputs.path }}" >/dev/null - APP="${MOUNT}/OpenScreen Subscription.app" + APP="${MOUNT}/PurplePrint Studio.app" test -d "$APP" test -L "${MOUNT}/Applications" codesign --verify --deep --strict "$APP" @@ -204,7 +204,7 @@ jobs: import { readFileSync, writeFileSync } from "node:fs"; const bytes = readFileSync(process.env.DMG); const report = { - product: "OpenScreen Subscription", + product: "PurplePrint Studio", version: process.env.VERSION, architecture: process.env.ARCH, bundleId: "io.github.purpleprintai.openscreen-subscription", diff --git a/.github/workflows/build-fork-windows.yml b/.github/workflows/build-fork-windows.yml new file mode 100644 index 000000000..0adeb7d01 --- /dev/null +++ b/.github/workflows/build-fork-windows.yml @@ -0,0 +1,93 @@ +name: Validate PurplePrint Studio for Windows + +on: + workflow_dispatch: + pull_request: + branches: ["fork/main"] + paths: + - ".github/workflows/build-fork-windows.yml" + - "build/fork-speech-payload.json" + - "crates/**" + - "electron/**" + - "scripts/**" + - "src/**" + - "package.json" + - "package-lock.json" + - "electron-builder.fork.json" + - "electron-builder.json5" + +permissions: + contents: read + actions: read + +jobs: + package: + name: Windows x64 clean package + runs-on: windows-latest + timeout-minutes: 90 + steps: + - name: Checkout source + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: ./.github/actions/setup + + - name: Check pinned LLVM toolchain + shell: pwsh + run: | + $clang = 'C:\Program Files\LLVM\bin\libclang.dll' + if (-not (Test-Path -LiteralPath $clang)) { + throw "libclang missing at $clang" + } + + - name: Build native helpers, renderer and unsigned NSIS package + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + CSC_IDENTITY_AUTO_DISCOVERY: "false" + run: npm run build:win:fork + + - name: Smoke-test packaged CLI + shell: pwsh + run: | + $version = (Get-Content package.json -Raw | ConvertFrom-Json).version + $exe = Join-Path "release/$version/win-unpacked" 'PurplePrint Studio.exe' + $process = Start-Process -FilePath $exe -ArgumentList 'help' -WindowStyle Hidden -Wait -PassThru -RedirectStandardOutput cli-help.txt -RedirectStandardError cli-stderr.txt + if ($process.ExitCode -ne 0) { throw "Packaged CLI exited $($process.ExitCode)" } + if (-not (Select-String -LiteralPath cli-help.txt -SimpleMatch 'PurplePrint Studio CLI (openscreen command)' -Quiet)) { + throw 'Packaged CLI banner missing' + } + + - name: Verify package identity, payload and checksums + shell: pwsh + env: + GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: node scripts/validate-fork-windows-package.mjs + + - name: Confirm candidate is unsigned + shell: pwsh + run: | + $version = (Get-Content package.json -Raw | ConvertFrom-Json).version + $setup = "release/$version/PurplePrint-Studio-$version-Windows-x64-Setup.exe" + $status = (Get-AuthenticodeSignature -LiteralPath $setup).Status + if ($status -ne 'NotSigned') { throw "Unexpected installer signature status: $status" } + + - name: Read candidate version + id: version + shell: pwsh + run: | + $version = (Get-Content package.json -Raw | ConvertFrom-Json).version + "value=$version" >> $env:GITHUB_OUTPUT + + - name: Upload validation-only Windows package + uses: actions/upload-artifact@v7 + with: + name: purpleprint-studio-windows-x64 + path: | + release/${{ steps.version.outputs.value }}/PurplePrint-Studio-${{ steps.version.outputs.value }}-Windows-x64-Setup.exe + release/${{ steps.version.outputs.value }}/PurplePrint-Studio-${{ steps.version.outputs.value }}-Windows-x64-Setup.exe.blockmap + release/${{ steps.version.outputs.value }}/latest.yml + release/${{ steps.version.outputs.value }}/windows-validation.json + release/${{ steps.version.outputs.value }}/SHA256SUMS-Windows.txt + if-no-files-found: error + retention-days: 30 diff --git a/.gitignore b/.gitignore index a1822ee05..7e99ab72f 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ release/** # Playwright test-results playwright-report/ +/.playwright-cli/ +/output/playwright/ # Vitest browser mode screenshots diff --git a/FORK-CHANGELOG.md b/FORK-CHANGELOG.md index 08a7597bb..b2150d33f 100644 --- a/FORK-CHANGELOG.md +++ b/FORK-CHANGELOG.md @@ -1,4 +1,4 @@ -# OpenScreen Subscription fork changelog +# PurplePrint Studio fork changelog This file tracks changes made by the unofficial [PurplePrintAI/openscreen-subscription](https://github.com/PurplePrintAI/openscreen-subscription) @@ -17,6 +17,84 @@ see [FORK.md](FORK.md). For the dated review of newer upstream changes, see [FORK-UPSTREAM-REVIEW.md](FORK-UPSTREAM-REVIEW.md). +## Unreleased — PurplePrint Studio identity (`1.10.0-subscription.11` candidate) + +- Adopts the PurplePrint GraphOS family's neutral surfaces, restrained violet + accents, typography hierarchy and product mark across the editor and capture + entry surfaces. Recording and semantic status colors retain their meanings. +- Renames visible app, installer, Start menu and macOS package labels to + PurplePrint Studio. The technical app ID, data profile, `.openscreen` format, + CLI command and GitHub update origin stay stable for compatibility. +- Keeps upstream attribution explicit. This work does not add Design Coach/MCP + integration or claim GraphOS runtime integration. +- Before live installation, the unsigned Windows NSIS package was smoke-tested + with an isolated profile. Packaged H.264 import, editor launch and MP4 export + passed with synthetic media. +- An isolated profile created by the installed `.7` fork retained its project, + media clip, light theme and preferences when opened by the Studio package; + the old project exported a decodable MP4. A packaged Windows WGC window + capture of a generated, non-user-content target also reopened and exported. + This exercised native capture with automated control. On the installed `.8` + app, the owner separately confirmed that a physical mouse click on the HUD's + Screen button opened the source selector. The owner then selected a source, + recorded six seconds, stopped from the HUD and opened the saved recording in + Studio. A user-driven export remains unverified. +- Fixes the packaged audio-demuxer WASM URL used by automatic transcription. + The installed `.8` opened a silent recording but showed a transient + "Failed to fetch" transcription error because this URL resolved outside the + packaged `dist/wasm` directory. After the `.9` upgrade, the owner recorded + another silent clip without the error toast; its new project persisted a + `no-audio` verdict and the owner exported an MP4 successfully. +- With the owner's approval, the `.8` NSIS installer upgraded the existing + per-user `.7` registration. It removed the old executable, installed Studio + in a product-named child folder beneath the prior custom location and replaced + the Start-menu shortcut. A local backup was verified before installation; + three existing project files and the AI configuration retained their hashes. + Studio launched using the existing locale. The installed `.8` binary loaded + an isolated copy of all three real project documents (three assets and three + clips) and opened the editor without emitting their contents. The live + profile was not used for this project-loading test; private footage was not + exported. +- Builds Apple Silicon and Intel macOS DMGs in + [the fork's validation workflow](https://github.com/PurplePrintAI/openscreen-subscription/actions/runs/36320426373). + Both passed architecture, native payload, bundle ID, ad-hoc signature, CLI + boot and mounted-DMG checks. The bundled installation guide uses the Studio + name and links Apple's current per-app security procedure. A real Mac record → + edit → export pass, Developer ID signing and notarization remain release + gates. No Studio release has been published. +- Advances the unpublished candidate to `1.10.0-subscription.9`. The `.8` + candidate refreshed the Nix dependency hash and checked the rebranded CLI + banner in macOS packaging. The unsigned `.9` Windows NSIS installer was built + and its packaged WASM path checked, then installed on the owner's PC after a + verified backup. Nine installed payload files matched the `.9` build, the + registered version was `.9`, and the Start-menu shortcut kept its target. + This developer PC + lacked `libclang.dll` for a fresh compositor rebuild, so packaging reused the + verified `.8` compositor binary with the same SHA-256; the WGC helper was + rebuilt. [macOS `.9` CI](https://github.com/PurplePrintAI/openscreen-subscription/actions/runs/36358715037) + built Apple Silicon and Intel DMGs and passed payload, bundle identity, + architecture, ad-hoc signature structure, CLI boot and mounted-DMG checks. + A real Mac record → edit → export pass and notarization remain unverified. +- Advances the Windows source candidate to `1.10.0-subscription.10`. Update + discovery now reads published releases in the installed `subscription` + prerelease channel; GitHub's `/releases/latest` endpoint returned 404 while + the fork had only prereleases. Drafts and unrelated channels are ignored, + versions are compared semantically, and release URLs remain restricted to + this fork. A clean Windows packaging workflow rebuilds the native helpers + and verifies the installer without publishing it. The Windows speech runtime + is pinned to a checksum-verified artifact built by this fork rather than an + upstream workflow artifact. +- The `.10` clean Windows package passed CI and was installed locally after a + verified `.9` backup. The owner reported a first recording attempt where the + three-second countdown ended but native capture had not yet acknowledged + startup; a second click received "already running". Retrying later recorded + and exported MP4. The `.11` candidate blocks overlapping start requests and + shows a localized "Starting…" spinner while waiting for the native helper. + The clean `.11` Windows package passed CI and was installed on the owner's PC + after a verified `.10` backup. The owner confirmed a successful test recording, + MP4 export and an installed-app "up to date" check. A real future-version + updater download/install remains unverified; `.11` is not published. + ## 1.10.0-subscription.7 — 2026-09-27 ### GPT-6 model catalog diff --git a/FORK.md b/FORK.md index 18b52eb9a..dcf72d3bc 100644 --- a/FORK.md +++ b/FORK.md @@ -1,6 +1,7 @@ -# OpenScreen Subscription — independent fork +# PurplePrint Studio — independent OpenScreen fork -This is an **unofficial, independently maintained fork** of +PurplePrint Studio (formerly OpenScreen Subscription) is an **unofficial, +independently maintained fork** of [getopenscreen/openscreen](https://github.com/getopenscreen/openscreen), initially based on upstream **v1.10.0** (`dbdadb7d27fe3287d994fb741d6f264a31066648`). It is not an official OpenScreen release and is not endorsed by the upstream @@ -18,8 +19,8 @@ architecture, policy and release notes behind those changes. - Retain API-key providers, editor tool validation and checkpoints. - Separate installation identity, user data and update downloads from upstream. -The app remains free. "Subscription" names the optional use of the user's own -eligible ChatGPT plan; this fork does not sell or require its own plan. Codex CLI +The app remains free. The former "Subscription" name described optional use of the +user's own eligible ChatGPT plan; Studio does not sell or require its own plan. Codex CLI is a user-installed dependency, not bundled in the installer. Tokens, API keys, user settings, recordings and diagnostic transcripts must never be committed or uploaded as release assets. @@ -55,7 +56,8 @@ are different actions. Do not represent a PR as accepted without actual review. 2. Build the WGC helper and compositor from this checkout. Keep native-payload and freshness checks enabled. Do not bypass them or retimestamp old binaries. 3. Fetch the pinned, checksum-verified LGPL FFmpeg SDK. Stage speech helpers from - a recorded upstream build; retain provenance and hashes. + the recorded fork build in `build/fork-speech-payload.json`; retain provenance + and hashes. 4. Run app/test typechecks, relevant tests, the full unit suite, i18n checks and native smoke tests. Resolve new failures; never relabel a failed check as passed. 5. Package with `electron-builder.fork.json` and `--publish never`. The original @@ -74,6 +76,13 @@ npm run build-vite npx electron-builder --win nsis --x64 --config electron-builder.fork.json --config.npmRebuild=false --publish never ``` +For a clean Windows runner build, use +[`build-fork-windows.yml`](.github/workflows/build-fork-windows.yml). Relevant +fork PR changes trigger it; after the workflow is on the default branch, it +can also be dispatched manually. It rebuilds the native helpers and unsigned +installer, checks the packaged payload against the build outputs, and uploads +validation-only artifacts without publishing a GitHub Release. + The installer is unsigned unless an independently authorized signing identity is configured. No upstream publisher identity is borrowed. Windows security prompts must be handled by the user, not disabled by this fork or its build scripts. diff --git a/MACOS-INSTALLATION.md b/MACOS-INSTALLATION.md index 37a0bc9ff..43f9bb654 100644 --- a/MACOS-INSTALLATION.md +++ b/MACOS-INSTALLATION.md @@ -1,8 +1,13 @@ -# OpenScreen Subscription on macOS +# PurplePrint Studio on macOS -OpenScreen Subscription is an unofficial fork. The current macOS packages are -validation prereleases with the fork bundle identifier and an ad-hoc hardened-runtime -signature. They are **not notarized by Apple**. +> This guide covers PurplePrint Studio validation builds, including the +> unpublished `1.10.0-subscription.8` candidate. It is not a stable consumer release. + +PurplePrint Studio is an unofficial OpenScreen fork. The current macOS packages +carry the fork bundle identifier and an ad-hoc hardened-runtime signature. They +are **not Developer ID signed or notarized by Apple**. Apple warns that running +software without those checks may put the Mac and its data at risk; use these +packages only for controlled testing from a source you trust. ## Choose the package @@ -15,11 +20,14 @@ preview and export would all run through translation. ## Install and open -1. Download the matching `.dmg` from this fork's GitHub prerelease. -2. Open the DMG and drag **OpenScreen Subscription** to **Applications**. -3. In Applications, Control-click **OpenScreen Subscription**, choose **Open**, and - confirm the first launch. This is the per-app Gatekeeper path for an unnotarized - validation build. Do not disable Gatekeeper globally. +1. Obtain the matching `.dmg` from the fork's validation workflow or an approved + prerelease. Compare its SHA-256 with the accompanying `SHA256SUMS-macOS.txt`. +2. Open the DMG and drag **PurplePrint Studio** to **Applications**. +3. Try opening **PurplePrint Studio** from Applications. If macOS blocks it and + you independently decide to test this unnotarized build despite the warning, + use Apple's [Privacy & Security → Open Anyway procedure](https://support.apple.com/en-au/102445) + after that attempt. Do not disable Gatekeeper globally or remove quarantine + attributes by command line. 4. Grant only the permissions needed for the features you use. macOS may request Screen & System Audio Recording, Accessibility, Microphone or Camera access. 5. Fully quit and reopen the app after changing a privacy permission. @@ -34,14 +42,14 @@ Codex CLI and Claude Code are optional external dependencies. They are never bun in the DMG. - ChatGPT subscription: install the official Codex CLI, then connect the account in - OpenScreen's AI settings. + Studio's AI settings. - Claude subscription: install the official Claude Code CLI and run - `claude auth login` in Terminal before checking the connection in OpenScreen. + `claude auth login` in Terminal before checking the connection in Studio. The packaged app checks the inherited PATH and common Finder-safe locations: `~/.local/bin`, `~/.npm-global/bin`, `~/.volta/bin`, `~/Library/pnpm`, `/opt/homebrew/bin` and `/usr/local/bin`. The corresponding command should work in -Terminal (`codex --version` or `claude --version`) before troubleshooting OpenScreen. +Terminal (`codex --version` or `claude --version`) before troubleshooting Studio. ## Known transcription limitation diff --git a/README.md b/README.md index 6916c4806..85483b288 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ -> **Unofficial fork: OpenScreen Subscription.** Adds local ChatGPT/Codex and Claude Code subscription routing, AI-session UI improvements, and a Windows Unicode recording-path fix. This is not an upstream release or endorsement. See [FORK.md](FORK.md) for attribution, policy and publication boundaries, and [FORK-CHANGELOG.md](FORK-CHANGELOG.md) for the versioned differences from upstream v1.10.0. Use this fork's release artifacts, not the upstream installers linked in the original documentation below. +> **PurplePrint Studio is an independent, unofficial fork of [OpenScreen](https://github.com/getopenscreen/openscreen).** This working rebrand is not an upstream release or endorsement. Published `1.10.0-subscription.*` installers still use the former OpenScreen Subscription name until a Studio build passes release validation. See [FORK.md](FORK.md) for provenance and [FORK-CHANGELOG.md](FORK-CHANGELOG.md) for changes. Use [this fork's releases](https://github.com/PurplePrintAI/openscreen-subscription/releases), not the upstream installers described later on this page.

- OpenScreen Logo + PurplePrint Studio mark

-#

OpenScreen

+#

PurplePrint Studio

-

A free, open-source desktop app for recording your screen and turning the result into polished product demos and walkthroughs.

+

Agentic Film Editor for product demos and GTM video content.

+ +PurplePrint Studio remains free to use. It records and edits locally; optional AI editing uses the user's own configured provider or local Codex/Claude Code runtime. The existing `.openscreen` project format and `openscreen` CLI command remain unchanged for compatibility. Design Coach handoff and GraphOS integration are not included in this rebrand. See the [Studio identity and release gates](docs/purpleprint-studio-identity.md). + +## OpenScreen upstream documentation — reference only + +The feature overview and installation sections below describe the upstream OpenScreen project. Its Store listings, signing claims and release links **do not apply to this fork**. [FORK.md](FORK.md) describes fork release boundaries; [MACOS-INSTALLATION.md](MACOS-INSTALLATION.md) currently documents the published packages under their former name. Studio packages still need validation. + +### OpenScreen

Editing a recording in OpenScreen: wallpaper and video effects, an AI-assisted cut driven from the chat, then export diff --git a/build/fork-speech-payload.json b/build/fork-speech-payload.json index 4e4d4ac06..2d97e17d1 100644 --- a/build/fork-speech-payload.json +++ b/build/fork-speech-payload.json @@ -1,37 +1,37 @@ { - "repository": "getopenscreen/openscreen", - "runId": 32717547572, - "headSha": "ed316fae986394e355959b3ac8e4318f1d07796e", + "repository": "PurplePrintAI/openscreen-subscription", + "runId": 36294782126, + "headSha": "93fce11ee2ae73b774063ed81e3b517b14087b31", "artifact": "whisper-stt-win32-x64", - "archiveSha256": "B60AB2C1531A23E990565E069EADAAE6511D299A91E20B1685A33378CB145136", + "archiveSha256": "92D6E9F8DE808BDDC0CC4B4D1799FDD495E1434EF5CD133BAAA05DDF96583EF1", "files": [ { "name": "ggml-base.dll", - "sha256": "4D67BEB0C5BC5A9FD746A0296BE1E11862755F95F229B28B077D36E969E559E0" + "sha256": "CCB20A9E14F54EEC24D799A0A750637894FDD93C783A9FCA32AD7D877D430CD9" }, { "name": "ggml-cpu.dll", - "sha256": "5CB48891BCC872484636D966D90E0C0FC82DE0E64228FA47D47321DB28712C1D" + "sha256": "75B3C517BA1CA27E38E2C6719F10469895E578BF729B914376EAFDD278C63B21" }, { "name": "ggml-vulkan.dll", - "sha256": "B1F61BB9FA3B9D78980AAB0C55E11CF2B65DA8A95ED5F6EB8E9D94664BAAF11D" + "sha256": "67B0419B768F05CE81084E1153094EFCBF7168CC03A031E4F4350985D159A1D9" }, { "name": "ggml.dll", - "sha256": "F718C3472B34D97E29FD82199DE72538ED6DD9439AFFB8A0F2514067F32AED6D" + "sha256": "63DC8A8AFE4D4FDCA9346F25D6EEE2908190C8C465D0D3867C681C2DA82A625C" }, { "name": "parakeet.dll", - "sha256": "3C5DF100C4041474E092E5BE35084A80DD10C6ED1B4ED9334F45BFFC7B71C230" + "sha256": "22AA903E520053EEE49438F6A5B1AE5002CF46857BDC7607AEB8E6A1AF39246C" }, { "name": "whisper-stt-server.exe", - "sha256": "52865EEAF2C99D2CE8A412366D81AF41104C7F07DAD47C18BEAC9A5F476F679B" + "sha256": "14F746233966955E9F2E5742A46087F17E6775F86AD55B1C97A541587D334249" }, { "name": "whisper.dll", - "sha256": "EFB7376BC5EB5D3FF904816EEC9A49768FD46B5E149D8C2A96989D4061843060" + "sha256": "0B7BBCB193CCEDCBFB9CAC6A3854E4E732C2C144918931CB3C3C13FD1C37197F" } ] } diff --git a/docs/purpleprint-studio-identity.md b/docs/purpleprint-studio-identity.md new file mode 100644 index 000000000..42a263fca --- /dev/null +++ b/docs/purpleprint-studio-identity.md @@ -0,0 +1,63 @@ +# PurplePrint Studio identity + +Status: unsigned `.11` Windows candidate installed on the owner's PC. The owner +confirmed recording, MP4 export and an "up to date" check in the installed app. +No Studio release has been published. + +PurplePrint Studio is the agentic film editor intended as a video-production +companion to PurplePrint Design Coach's GTM and operating phases. GraphOS is the +developing top-level PurplePrint product. This fork does **not** currently share +GraphOS runtime state or consume Design Coach/MCP project context; that handoff +belongs to the Design Coach/MCP workstream, not this frontend identity change. + +## Visual contract + +- Neutral dark surfaces are the default. Light mode remains available and an + existing explicit light preference is respected. +- Violet identifies the PurplePrint family, focus, selected tools and primary + actions. It does not replace the functional colors for recording, progress, + success, warnings or errors. +- Borders and small elevation steps carry panel hierarchy. Overlays can be more + prominent, while the editing surface and footage remain the visual focus. +- The editor's minimum 800px desktop width retains mode and export controls; + chat and the inspector start collapsed when their width would crowd the stage. + +The implementation lives in `src/styles/design-tokens.css` and the active editor +and capture surfaces. The checked-in Studio icon assets are generated from +`public/purpleprint-studio.svg` geometry by +`scripts/generate-studio-icons.py` (Pillow is needed only to regenerate them). + +## Compatibility and provenance + +- Visible product name: **PurplePrint Studio**. Descriptor: **Agentic Film Editor**. +- The fork's technical app ID, package/data profile name, GitHub update origin, + `.openscreen` project extension and `openscreen` CLI command remain unchanged. + Test these boundaries in a packaged upgrade before a public release. +- Preserve the upstream MIT license, copyright notices and independent-fork + disclosure. Do not imply OpenScreen maintainer endorsement. +- Do not add a paywall to this editor as part of the Design Coach bundle. + +## Release gate + +The `.11` clean Windows package and owner-driven recording → MP4 export passed +from the product-named installation directory. Its manual update check reports +"up to date"; a real future-version updater download/install cannot be tested +until a later release exists. Decide how to handle the unsigned Windows +installer before public publication. +A separate macOS release still needs a real Mac record → edit → export pass. +A stable Mac consumer release additionally needs Developer ID signing, +notarization and a privacy-permission review. Design Coach/MCP linkage is a +separate workstream. + +| Gate | Current evidence | +| --- | --- | +| Windows package identity | The `.11` unsigned NSIS package was built cleanly on a Windows CI runner with libclang. Owner-approved installation reused the existing per-user location without another nested folder. Uninstall registration and installed ASAR report `.11`; the Start-menu shortcut points to the installed executable. Seven installed speech runtime files match the fork-pinned manifest. | +| Profile continuity | Installed OpenScreen Subscription `.7` created an isolated-profile project with synthetic H.264 media. Studio `.8` reopened its clip, light theme and preferences, then exported a decodable MP4. On the owner's live profile, three project documents and the AI configuration were hash-identical to the verified pre-install backup. The installed `.8` binary separately loaded copies of all three real project documents and opened the editor without outputting their contents; private footage was not exported. | +| Windows native capture | Packaged WGC recorded a generated QA window with microphone, system audio and camera disabled. The saved project reopened and exported a decodable MP4. Control was automated below the OS hit-test. Installed `.8` and `.9` were also recorded by the owner with a physical mouse; `.9` exported MP4 successfully. On installed `.10`, the owner reported that the first attempt showed "already running" after clicking Record again while the native helper had not acknowledged startup; retrying recorded and exported MP4. `.11` blocks this overlap and shows "Starting…" while the helper starts. The owner reported a successful `.11` test recording and MP4 export; visibility of the brief starting label was not separately confirmed. | +| Silent-recording transcription | The `.8` recording contained video but no audio stream. Automatic transcription showed "Failed to fetch" because its WASM URL resolved outside packaged `dist/wasm`. Installed `.9` corrected the URL: another video-only recording produced no error toast, and its saved asset carries the expected `no-audio` verdict. | +| Installation upgrade | Owner-approved `.8` NSIS upgrade reused the per-user uninstall registration and created a product-named child folder under the previous custom root. Subsequent `.9`, `.10` and `.11` upgrades reused that exact folder. Before `.11`, 2,868 profile files, 188 installed files and a `.10` rollback installer were backed up and verified; six critical project/recording/AI-config hashes remained unchanged after upgrade. `.10` fixed release discovery for the fork's `subscription` prereleases; the installed `.11` manual update check reports "up to date". A real future-version updater download/install remains unverified. | +| macOS | [CI run 36358715037](https://github.com/PurplePrintAI/openscreen-subscription/actions/runs/36358715037) built `.9` Apple Silicon and Intel DMGs from commit `f81a8b9d`. Both passed bundle ID, architecture, native payload, ad-hoc signature structure, CLI boot and mounted-DMG checks; downloaded SHA-256 values matched the workflow report. Real Mac recording/edit/export, Developer ID signing and notarization remain unverified. | + +The source selector failed to list the QA window on the first automated attempt +after it appeared, then found it on retry. Treat that as a transient observation, +not evidence that source discovery is reliable on every launch. diff --git a/electron-builder.fork.json b/electron-builder.fork.json index 8fbb6aa8a..42c65e600 100644 --- a/electron-builder.fork.json +++ b/electron-builder.fork.json @@ -1,10 +1,10 @@ { "extends": "./electron-builder.json5", "appId": "io.github.purpleprintai.openscreen-subscription", - "productName": "OpenScreen Subscription", + "productName": "PurplePrint Studio", "extraMetadata": { "name": "openscreen-subscription", - "description": "Unofficial OpenScreen fork with optional ChatGPT and local Claude integration", + "description": "PurplePrint Studio, an agentic film editor based on OpenScreen", "author": { "name": "PurplePrintAI", "url": "https://github.com/PurplePrintAI" }, "homepage": "https://github.com/PurplePrintAI/openscreen-subscription", "repository": { @@ -15,18 +15,18 @@ "publish": { "provider": "github", "owner": "PurplePrintAI", "repo": "openscreen-subscription" }, "win": { "target": ["nsis"], - "artifactName": "OpenScreen-Subscription-${version}-Windows-x64-Setup.${ext}" + "artifactName": "PurplePrint-Studio-${version}-Windows-x64-Setup.${ext}" }, "mac": { "target": ["dmg"], - "artifactName": "OpenScreen-Subscription-${version}-macOS-${arch}.${ext}" + "artifactName": "PurplePrint-Studio-${version}-macOS-${arch}.${ext}" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createStartMenuShortcut": true, "createDesktopShortcut": false, - "shortcutName": "OpenScreen Subscription" + "shortcutName": "PurplePrint Studio" }, "extraResources": [ { "from": "FORK.md", "to": "FORK.md" }, diff --git a/electron/about.test.ts b/electron/about.test.ts index 96e401630..d7b271f2e 100644 --- a/electron/about.test.ts +++ b/electron/about.test.ts @@ -4,6 +4,7 @@ import { type AboutFacts, COPYRIGHT, formatAboutDetail, + PRODUCT_NAME, usesNativeAboutPanel, WEBSITE_URL, } from "./about"; @@ -22,6 +23,11 @@ function facts(overrides: Partial = {}): AboutFacts { } describe("formatAboutDetail", () => { + it("uses the Studio display identity without dropping upstream provenance", () => { + expect(PRODUCT_NAME).toBe("PurplePrint Studio"); + expect(formatAboutDetail(facts())).toContain("Unofficial fork of getopenscreen/openscreen"); + }); + it("lays the runtime, the install and the project out one per line", () => { expect(formatAboutDetail(facts())).toBe( [ diff --git a/electron/about.ts b/electron/about.ts index 897748851..6231495a4 100644 --- a/electron/about.ts +++ b/electron/about.ts @@ -9,10 +9,9 @@ import type { InstallChannel } from "./install-channel"; export const WEBSITE_URL = "https://github.com/PurplePrintAI/openscreen-subscription"; -/** The brand spelling, for the surfaces we render ourselves. NOT `app.name`: that resolves to - * electron-builder's `productName` ("Openscreen") when packaged and to package.json's `name` - * ("openscreen") in dev, so the About box would disagree with its own title bar. */ -export const PRODUCT_NAME = "OpenScreen Subscription"; +/** The brand spelling for our own surfaces. `app.name` can resolve differently in dev and + * packaged builds, so the About box must not derive its title from that value. */ +export const PRODUCT_NAME = "PurplePrint Studio"; /** The collective form, and deliberately NOT the whole of LICENSE. LICENSE carries two holders: * Siddharth Vaddem, who created the project — MIT obliges us to keep that notice on a codebase * that still contains his code — and the contributors collectively. This is the line every user @@ -45,7 +44,7 @@ export interface AboutFacts { node: string; } -/** The block under "Openscreen ". Untranslated on purpose: every line is a version +/** The block under "PurplePrint Studio ". Untranslated on purpose: every line is a version * number, a platform identifier or a URL, and a pasted bug report reads the same whatever * locale the reporter runs. * diff --git a/electron/ai-edition/agent-tools.ts b/electron/ai-edition/agent-tools.ts index 5a0966398..ef4c58188 100644 --- a/electron/ai-edition/agent-tools.ts +++ b/electron/ai-edition/agent-tools.ts @@ -1149,7 +1149,7 @@ export function executeAgentTool( assetId: load.assetId, note: "Checked: this asset has no cursor-telemetry sidecar. That normally means it " + - "was imported rather than recorded with OpenScreen's cursor recorder. This is " + + "was imported rather than recorded with PurplePrint Studio's cursor recorder. This is " + "a fact about the asset, not a limit of yours.", }), }; diff --git a/electron/ai-edition/claude/cli.ts b/electron/ai-edition/claude/cli.ts index a8524f3eb..ebc8f29a3 100644 --- a/electron/ai-edition/claude/cli.ts +++ b/electron/ai-edition/claude/cli.ts @@ -368,12 +368,12 @@ export class ClaudeCli { ) ) { throw new Error( - "Claude enabled tools outside the OpenScreen editor. Check your CLI managed configuration.", + "Claude enabled tools outside the PurplePrint Studio editor. Check your CLI managed configuration.", ); } if (expectedTools.some((name) => !(event.tools as unknown[]).includes(name))) { throw new Error( - "Claude could not load the OpenScreen editor tools. Check your CLI MCP policy and connection.", + "Claude could not load the PurplePrint Studio editor tools. Check your CLI MCP policy and connection.", ); } } diff --git a/electron/ai-edition/codex/app-server.ts b/electron/ai-edition/codex/app-server.ts index 6b4a5ecb7..a2a2a78e2 100644 --- a/electron/ai-edition/codex/app-server.ts +++ b/electron/ai-edition/codex/app-server.ts @@ -177,7 +177,7 @@ export class CodexAppServer { }); try { await this.request("initialize", { - clientInfo: { name: "openscreen_subscription", title: "OpenScreen Subscription", version }, + clientInfo: { name: "openscreen_subscription", title: "PurplePrint Studio", version }, capabilities: { experimentalApi: true }, }); this.send({ method: "initialized", params: {} }); @@ -226,7 +226,7 @@ export class CodexAppServer { if (payload.id !== undefined) { if (payload.method === "item/tool/call" && turn) { const tool = turn.tools.get(String(params.tool)); - let text = "Tool is not available in this OpenScreen turn."; + let text = "Tool is not available in this PurplePrint Studio turn."; let success = false; if (tool && ++turn.toolCalls <= 1000) { try { @@ -245,7 +245,7 @@ export class CodexAppServer { // No shell, file edits, remote MCP approvals or other vendor tools are approved here. this.send({ id: payload.id, - error: { code: -32601, message: "Only OpenScreen editor tools are supported." }, + error: { code: -32601, message: "Only PurplePrint Studio editor tools are supported." }, }); } return; @@ -288,7 +288,7 @@ export class CodexAppServer { child?.kill(); } close(): void { - this.fail(new Error("OpenScreen closed the Codex connection.")); + this.fail(new Error("PurplePrint Studio closed the Codex connection.")); } async status(): Promise { diff --git a/electron/ai-edition/deep-agent/service.ts b/electron/ai-edition/deep-agent/service.ts index b1a15c908..2dd4dcce1 100644 --- a/electron/ai-edition/deep-agent/service.ts +++ b/electron/ai-edition/deep-agent/service.ts @@ -100,7 +100,7 @@ const CONSENT_PROMPT_BLOCK = [ // string and NOTHING else — the deepagents regression was invisible precisely // because the middlewares appended their prompts downstream of this constant. const BASE_SYSTEM_PROMPT = [ - "You are an AI video editor working inside OpenScreen. The user is editing a recording.", + "You are an AI video editor working inside PurplePrint Studio. The user is editing a recording.", "Help them cut silences, tighten pacing, add captions, and rewrite titles.", "Be concise, action-oriented, and reference the timeline or transcript by time when relevant.", "You can call the tools below against the live document snapshot; the runtime executes each edit and feeds the result back into the loop.", @@ -121,7 +121,7 @@ const BASE_SYSTEM_PROMPT = [ "- Deleting is a first-class action, not a workaround: removeTrim, removeModifier, removeClip. Never fake a deletion by re-adding an element or zeroing it out (span 0, speed 1×) — that leaves it in the document and misreports what you did.", "If nothing in the list does what was asked, say so; do not approximate it with a bigger tool.", "", - "Cursor telemetry: while the screen was captured, OpenScreen recorded where the pointer went. assets[].hasCursorTelemetry in getCurrentDocument says which assets carry it, and getCursorTrack returns the recorded track for one of them — positions over time, and the pointer shape at each moment. What it means is yours to read.", + "Cursor telemetry: while the screen was captured, PurplePrint Studio recorded where the pointer went. assets[].hasCursorTelemetry in getCurrentDocument says which assets carry it, and getCursorTrack returns the recorded track for one of them — positions over time, and the pointer shape at each moment. What it means is yours to read.", "Blindness is not evidence. When a tool reports it could not read something (reason 'unavailable'), that is a limit of your runtime, not a fact about the project. Only an explicit negative — reason 'no-sidecar', or a false flag — supports telling the user the data is not there.", "", "Honesty rules: if a request has NO matching tool (e.g. deleting an asset/recording, exporting), say so plainly — do not substitute a different edit and report it as the requested one. After your edits, if you are at all unsure the document ended up as intended, call getCurrentDocument and reconcile what you claim with the real state; each tool result already tells you exactly what it did, so never report a change the results don't support.", diff --git a/electron/cli/args.ts b/electron/cli/args.ts index d9e33819e..67e7ed3f2 100644 --- a/electron/cli/args.ts +++ b/electron/cli/args.ts @@ -61,7 +61,7 @@ const SUBCOMMANDS = new Set([ "-h", ]); -export const CLI_USAGE = `OpenScreen CLI +export const CLI_USAGE = `PurplePrint Studio CLI (openscreen command) Usage: openscreen export [options] Render a project to MP4/GIF diff --git a/electron/fork-release.test.ts b/electron/fork-release.test.ts index 0a9adc623..711d93593 100644 --- a/electron/fork-release.test.ts +++ b/electron/fork-release.test.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import path from "node:path"; import { getConfig } from "app-builder-lib/out/util/config/config"; import { describe, expect, it } from "vitest"; @@ -5,7 +6,9 @@ import { describe, expect, it } from "vitest"; describe("fork release identity", () => { it("keeps installation and update identity separate from upstream", async () => { const config = await getConfig(process.cwd(), path.resolve("electron-builder.fork.json"), null); - expect(config.appId).not.toBe("com.etiennelescot.openscreen"); + // The technical ID and profile name stay stable across the visible rebrand. + expect(config.appId).toBe("io.github.purpleprintai.openscreen-subscription"); + expect(config.productName).toBe("PurplePrint Studio"); expect(config.extraMetadata?.name).toBe("openscreen-subscription"); expect(config.extraMetadata?.author).toEqual({ name: "PurplePrintAI", @@ -18,6 +21,7 @@ describe("fork release identity", () => { repo: "openscreen-subscription", }); expect(config.nsis?.createStartMenuShortcut).toBe(true); + expect(config.nsis?.shortcutName).toBe("PurplePrint Studio"); const targets = (config.extraResources as Array<{ to: string }>).map((entry) => entry.to); // Duplicate resource targets race in copyFile on Windows. expect(new Set(targets).size).toBe(targets.length); @@ -29,5 +33,9 @@ describe("fork release identity", () => { "FORK-UPSTREAM-REVIEW.md", ]), ); + const macGuide = fs.readFileSync(path.resolve("MACOS-INSTALLATION.md"), "utf8"); + expect(macGuide).toContain(`# ${config.productName} on macOS`); + expect(macGuide).toContain(`drag **${config.productName}** to **Applications**`); + expect(macGuide).not.toContain("OpenScreen Subscription"); }); }); diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 958ef6d96..aaa6febfe 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -1920,7 +1920,7 @@ export function registerIpcHandlers( const detail = access.status === "missing-helper" ? "The cursor helper couldn't be found in this build, so the editable cursor can't be enabled. Rebuild the native helper (npm run build:native:mac) or switch the HUD cursor mode to system." - : "Allow OpenScreen under System Settings → Privacy & Security → Accessibility, then press record again to start the countdown."; + : "Allow PurplePrint Studio under System Settings → Privacy & Security → Accessibility, then press record again to start the countdown."; const messageOptions = { type: "warning", buttons: ["Open Accessibility Settings", "Cancel"], @@ -1968,7 +1968,7 @@ export function registerIpcHandlers( cancelId: 1, message: "Screen Recording permission is required", detail: - "Allow OpenScreen in macOS System Settings, then come back and choose a screen or window.", + "Allow PurplePrint Studio in macOS System Settings, then come back and choose a screen or window.", } satisfies Electron.MessageBoxOptions; const result = mainWin && !mainWin.isDestroyed() @@ -3962,7 +3962,7 @@ export function registerIpcHandlers( } // Validate extension and readability if (path.extname(filePath).toLowerCase() !== `.${PROJECT_FILE_EXTENSION}`) { - return { success: false, message: "Not an Openscreen project file" }; + return { success: false, message: "Not a .openscreen project file" }; } const stats = await fs.stat(filePath).catch(() => null); if (!stats?.isFile()) { diff --git a/electron/main.ts b/electron/main.ts index a85629bf3..162c57978 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -132,7 +132,7 @@ const isMac = process.platform === "darwin"; const trayIconSize = isMac ? 16 : 24; // Tray Icons -const defaultTrayIcon = getTrayIcon("openscreen.png", trayIconSize); +const defaultTrayIcon = getTrayIcon("purpleprint-studio.png", trayIconSize); const recordingTrayIcon = getTrayIcon("rec-button.png", trayIconSize); function createWindow() { @@ -209,11 +209,11 @@ function setupApplicationMenu() { if (isMac) { template.push({ - label: app.name, + label: PRODUCT_NAME, submenu: [ { role: "about", - label: mainT("common", "actions.about") || "About OpenScreen", + label: mainT("common", "actions.about") || "About PurplePrint Studio", }, { type: "separator" as const }, { @@ -239,7 +239,7 @@ function setupApplicationMenu() { { type: "separator" }, { role: "hide", - label: mainT("common", "actions.hide") || "Hide OpenScreen", + label: mainT("common", "actions.hide") || "Hide PurplePrint Studio", }, { role: "hideOthers", @@ -375,7 +375,7 @@ function setupApplicationMenu() { ] : []), { - label: mainT("common", "actions.about") || "About OpenScreen", + label: mainT("common", "actions.about") || "About PurplePrint Studio", click: runAboutDialog, }, { type: "separator" as const }, @@ -504,7 +504,7 @@ async function presentAboutDialog() { const heading = `${PRODUCT_NAME} ${facts.version}`; const choice = await showMessageBox({ type: "info", - title: mainT("common", "actions.about") || "About OpenScreen", + title: mainT("common", "actions.about") || "About PurplePrint Studio", message: heading, detail, buttons: [ @@ -779,10 +779,10 @@ function updateTrayMenu(recording: boolean = false) { isMac ? { role: "about" as const, - label: mainT("common", "actions.about") || "About OpenScreen", + label: mainT("common", "actions.about") || "About PurplePrint Studio", } : { - label: mainT("common", "actions.about") || "About OpenScreen", + label: mainT("common", "actions.about") || "About PurplePrint Studio", click: runAboutDialog, }, // Right next to About, and reachable without opening any window: this is the diff --git a/electron/native-bridge/services/aiEditionService.ts b/electron/native-bridge/services/aiEditionService.ts index 4393dc592..30a882cf4 100644 --- a/electron/native-bridge/services/aiEditionService.ts +++ b/electron/native-bridge/services/aiEditionService.ts @@ -243,7 +243,7 @@ export class AiEditionService { return { success: false, error: - "Manage Claude authentication in the official CLI. OpenScreen does not store its credentials.", + "Manage Claude authentication in the official CLI. PurplePrint Studio does not store its credentials.", }; if (providerId === "codex-subscription") return { success: false, error: "Use ChatGPT sign-in, not an API key." }; diff --git a/electron/update-checker.test.ts b/electron/update-checker.test.ts index 3fbd65e35..ff6528a25 100644 --- a/electron/update-checker.test.ts +++ b/electron/update-checker.test.ts @@ -9,6 +9,22 @@ function releaseResponse(payload: unknown, status = 200) { }; } +const RELEASES_API = + "https://api.github.com/repos/PurplePrintAI/openscreen-subscription/releases?per_page=100"; + +function release( + tag: string, + overrides: Partial<{ html_url: string; draft: boolean; prerelease: boolean }> = {}, +) { + return { + tag_name: tag, + html_url: `https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/${tag}`, + draft: false, + prerelease: tag.includes("-"), + ...overrides, + }; +} + describe("compareVersions", () => { it("implements semantic version ordering for stable and prerelease builds", () => { expect(compareVersions("v2.0.0", "1.9.9")).toBeGreaterThan(0); @@ -32,123 +48,113 @@ describe("compareVersions", () => { }); describe("checkLatestRelease", () => { - it("reports a newer official stable release", async () => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v1.10.0", - html_url: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.10.0", - draft: false, - prerelease: false, - }), - ); + it("selects the highest published release in the installed prerelease channel", async () => { + const fetchLatest = vi + .fn() + .mockResolvedValue( + releaseResponse([ + release("v1.10.0-subscription.6"), + release("v1.10.0-beta.20"), + release("v1.10.0-subscription.10", { draft: true }), + release("v1.10.0-subscription.9"), + release("v1.10.0-subscription.8"), + release("v1.10.0"), + ]), + ); - await expect(checkLatestRelease({ currentVersion: "1.9.0", fetchLatest })).resolves.toEqual({ + await expect( + checkLatestRelease({ currentVersion: "1.10.0-subscription.7", fetchLatest }), + ).resolves.toEqual({ kind: "available", - currentVersion: "1.9.0", - latestVersion: "1.10.0", - releaseUrl: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.10.0", + currentVersion: "1.10.0-subscription.7", + latestVersion: "1.10.0-subscription.9", + releaseUrl: + "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.10.0-subscription.9", }); expect(fetchLatest).toHaveBeenCalledWith( - "https://api.github.com/repos/PurplePrintAI/openscreen-subscription/releases/latest", + RELEASES_API, expect.objectContaining({ headers: expect.objectContaining({ Accept: "application/vnd.github+json" }), }), ); }); - it("reports current when the installed version is equal or newer", async () => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v1.9.0", - html_url: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.9.0", - draft: false, - prerelease: false, - }), - ); + it("reports current when only older subscription releases exist", async () => { + const fetchLatest = vi + .fn() + .mockResolvedValue(releaseResponse([release("v1.10.0-subscription.7")])); + await expect( + checkLatestRelease({ currentVersion: "1.10.0-subscription.9", fetchLatest }), + ).resolves.toEqual({ + kind: "current", + currentVersion: "1.10.0-subscription.9", + latestVersion: "1.10.0-subscription.7", + }); + }); - await expect(checkLatestRelease({ currentVersion: "1.9.1", fetchLatest })).resolves.toEqual({ + it("reports current when the fork has no published releases", async () => { + const fetchLatest = vi.fn().mockResolvedValue(releaseResponse([])); + await expect( + checkLatestRelease({ currentVersion: "1.10.0-subscription.9", fetchLatest }), + ).resolves.toEqual({ kind: "current", - currentVersion: "1.9.1", - latestVersion: "1.9.0", + currentVersion: "1.10.0-subscription.9", + latestVersion: "1.10.0-subscription.9", }); + }); + + it("keeps stable installs on the stable channel", async () => { + const fetchLatest = vi + .fn() + .mockResolvedValue(releaseResponse([release("v1.11.0-subscription.2"), release("v1.10.0")])); await expect(checkLatestRelease({ currentVersion: "1.9.0", fetchLatest })).resolves.toEqual({ - kind: "current", + kind: "available", currentVersion: "1.9.0", - latestVersion: "1.9.0", + latestVersion: "1.10.0", + releaseUrl: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.10.0", }); }); + it("ignores drafts, other channels, and malformed entries", async () => { + const fetchLatest = vi + .fn() + .mockResolvedValue( + releaseResponse([ + release("v1.10.0-subscription.12", { draft: true }), + { tag_name: "not a version", html_url: "https://example.com", draft: false }, + release("v1.10.0-beta.11"), + release("v1.10.0-subscription.8"), + ]), + ); + await expect( + checkLatestRelease({ currentVersion: "1.10.0-subscription.7", fetchLatest }), + ).resolves.toMatchObject({ kind: "available", latestVersion: "1.10.0-subscription.8" }); + }); + it("forwards the cancellation signal", async () => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v1.9.0", - html_url: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v1.9.0", - draft: false, - prerelease: false, - }), - ); + const fetchLatest = vi.fn().mockResolvedValue(releaseResponse([])); const controller = new AbortController(); - await checkLatestRelease({ - currentVersion: "1.9.0", + currentVersion: "1.10.0-subscription.9", fetchLatest, signal: controller.signal, }); - expect(fetchLatest).toHaveBeenCalledWith( - "https://api.github.com/repos/PurplePrintAI/openscreen-subscription/releases/latest", + RELEASES_API, expect.objectContaining({ signal: controller.signal }), ); }); it.each([ - { draft: true, prerelease: false }, - { draft: false, prerelease: true }, - ])("rejects draft and prerelease payloads: %o", async ({ draft, prerelease }) => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v2.0.0", - html_url: "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v2.0.0", - draft, - prerelease, - }), - ); - - await expect(checkLatestRelease({ currentVersion: "1.9.0", fetchLatest })).rejects.toThrow( - "invalid GitHub release response", - ); - }); - - it("rejects a release URL outside the official repository", async () => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v9.9.9", - html_url: "https://example.com/openscreen-9.9.9.exe", - draft: false, - prerelease: false, - }), - ); - - await expect(checkLatestRelease({ currentVersion: "1.9.0", fetchLatest })).rejects.toThrow( - "untrusted release URL", - ); - }); - - it.each([ + "https://example.com/openscreen-9.9.9.exe", "https://github.com/PurplePrintAI/openscreen-subscription/releases/download/v9.9.9/app.zip", "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v9.9.8", "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v9.9.9?download=1", "https://github.com/PurplePrintAI/openscreen-subscription/releases/tag/v9.9.9#notes", - ])("rejects an invalid official-repository URL: %s", async (htmlUrl) => { - const fetchLatest = vi.fn().mockResolvedValue( - releaseResponse({ - tag_name: "v9.9.9", - html_url: htmlUrl, - draft: false, - prerelease: false, - }), - ); - + ])("rejects an untrusted candidate release URL: %s", async (htmlUrl) => { + const fetchLatest = vi + .fn() + .mockResolvedValue(releaseResponse([release("v9.9.9", { html_url: htmlUrl })])); await expect(checkLatestRelease({ currentVersion: "1.9.0", fetchLatest })).rejects.toThrow( "untrusted release URL", ); @@ -159,7 +165,6 @@ describe("checkLatestRelease", () => { await expect( checkLatestRelease({ currentVersion: "1.9.0", fetchLatest: unavailable }), ).rejects.toThrow("GitHub release check failed (503)"); - const malformed = vi.fn().mockResolvedValue(releaseResponse({ tag_name: "v2.0.0" })); await expect( checkLatestRelease({ currentVersion: "1.9.0", fetchLatest: malformed }), diff --git a/electron/update-checker.ts b/electron/update-checker.ts index 7ea9070b8..ca201f09a 100644 --- a/electron/update-checker.ts +++ b/electron/update-checker.ts @@ -1,5 +1,8 @@ -const LATEST_RELEASE_API = - "https://api.github.com/repos/PurplePrintAI/openscreen-subscription/releases/latest"; +// GitHub's /releases/latest omits prereleases. This fork currently publishes +// subscription.N builds as prereleases, so that endpoint returns 404 even when +// the app has an update feed. Inspect the published releases instead. +const RELEASES_API = + "https://api.github.com/repos/PurplePrintAI/openscreen-subscription/releases?per_page=100"; const OFFICIAL_RELEASE_PREFIX = "/PurplePrintAI/openscreen-subscription/releases/tag/"; interface ReleaseResponse { @@ -124,7 +127,7 @@ export async function checkLatestRelease(options: { fetchLatest: FetchLatestRelease; signal?: AbortSignal; }): Promise { - const response = await options.fetchLatest(LATEST_RELEASE_API, { + const response = await options.fetchLatest(RELEASES_API, { headers: { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", @@ -134,32 +137,38 @@ export async function checkLatestRelease(options: { if (!response.ok) throw new Error(`GitHub release check failed (${response.status})`); const payload = await response.json(); - if ( - typeof payload !== "object" || - payload === null || - typeof (payload as Record).tag_name !== "string" || - typeof (payload as Record).html_url !== "string" || - (payload as Record).draft !== false || - (payload as Record).prerelease !== false - ) { - throw new Error("invalid GitHub release response"); - } - const release = payload as { tag_name: string; html_url: string }; + if (!Array.isArray(payload)) throw new Error("invalid GitHub release response"); const current = parseVersion(options.currentVersion); - const latest = parseVersion(release.tag_name); - const comparison = compareVersions(latest.normalized, current.normalized); - if (comparison <= 0) { + const channel = current.prerelease[0] ?? null; + let latest: { version: string; tag: string; url: string } | null = null; + for (const entry of payload) { + if (typeof entry !== "object" || entry === null || entry.draft !== false) continue; + if (typeof entry.tag_name !== "string" || typeof entry.html_url !== "string") continue; + let version: ParsedVersion; + try { + version = parseVersion(entry.tag_name); + } catch { + continue; + } + // Match electron-updater's GitHub channel: stable installs stay stable; + // subscription prereleases only advance within the subscription channel. + if ((version.prerelease[0] ?? null) !== channel) continue; + if (!latest || compareVersions(version.normalized, latest.version) > 0) { + latest = { version: version.normalized, tag: entry.tag_name, url: entry.html_url }; + } + } + if (!latest || compareVersions(latest.version, current.normalized) <= 0) { return { kind: "current", currentVersion: current.normalized, - latestVersion: latest.normalized, + latestVersion: latest?.version ?? current.normalized, }; } return { kind: "available", currentVersion: current.normalized, - latestVersion: latest.normalized, - releaseUrl: officialReleaseUrl(release.html_url, release.tag_name), + latestVersion: latest.version, + releaseUrl: officialReleaseUrl(latest.url, latest.tag), }; } diff --git a/icons/icons/mac/icon.icns b/icons/icons/mac/icon.icns index 02de106cf..9a376a3f3 100644 Binary files a/icons/icons/mac/icon.icns and b/icons/icons/mac/icon.icns differ diff --git a/icons/icons/png/1024x1024.png b/icons/icons/png/1024x1024.png index 447f2d147..3c567976b 100644 Binary files a/icons/icons/png/1024x1024.png and b/icons/icons/png/1024x1024.png differ diff --git a/icons/icons/png/128x128.png b/icons/icons/png/128x128.png index a9d9acf24..fadb14dfc 100644 Binary files a/icons/icons/png/128x128.png and b/icons/icons/png/128x128.png differ diff --git a/icons/icons/png/16x16.png b/icons/icons/png/16x16.png index 4b8fe811a..194f657a2 100644 Binary files a/icons/icons/png/16x16.png and b/icons/icons/png/16x16.png differ diff --git a/icons/icons/png/24x24.png b/icons/icons/png/24x24.png index ebffa992c..0296ff095 100644 Binary files a/icons/icons/png/24x24.png and b/icons/icons/png/24x24.png differ diff --git a/icons/icons/png/256x256.png b/icons/icons/png/256x256.png index 727d064c6..b96680f76 100644 Binary files a/icons/icons/png/256x256.png and b/icons/icons/png/256x256.png differ diff --git a/icons/icons/png/32x32.png b/icons/icons/png/32x32.png index 3f04dd210..d5ab783fc 100644 Binary files a/icons/icons/png/32x32.png and b/icons/icons/png/32x32.png differ diff --git a/icons/icons/png/48x48.png b/icons/icons/png/48x48.png index 4814b0525..cfbb82ba7 100644 Binary files a/icons/icons/png/48x48.png and b/icons/icons/png/48x48.png differ diff --git a/icons/icons/png/512x512.png b/icons/icons/png/512x512.png index 7ec2a0b4c..d15169965 100644 Binary files a/icons/icons/png/512x512.png and b/icons/icons/png/512x512.png differ diff --git a/icons/icons/png/64x64.png b/icons/icons/png/64x64.png index be64b4dbc..e05e8ceab 100644 Binary files a/icons/icons/png/64x64.png and b/icons/icons/png/64x64.png differ diff --git a/icons/icons/win/icon.ico b/icons/icons/win/icon.ico index 57df136d6..86daae6aa 100644 Binary files a/icons/icons/win/icon.ico and b/icons/icons/win/icon.ico differ diff --git a/index.html b/index.html index a5d20360c..eded762b0 100644 --- a/index.html +++ b/index.html @@ -2,25 +2,29 @@ - + - + PurplePrint Studio - +

diff --git a/nix/package.nix b/nix/package.nix index 31f8710bc..8859f5051 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -51,7 +51,7 @@ buildNpmPackage { ); }; - npmDepsHash = "sha256-XnI/b4OSUAhY3IeiHZNhurqJfpJ6H10AwR04ZGBLad4="; + npmDepsHash = "sha256-MNNWND3d3cZLGvCjU04ZgPPrvnZPtfjD/K9B/mYQA4Q="; env.ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; diff --git a/package-lock.json b/package-lock.json index 94739c7d3..bdf64d5ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openscreen", - "version": "1.10.0-subscription.7", + "version": "1.10.0-subscription.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openscreen", - "version": "1.10.0-subscription.7", + "version": "1.10.0-subscription.11", "license": "MIT", "dependencies": { "@fix-webm-duration/fix": "^1.0.1", diff --git a/package.json b/package.json index 423c9efdf..25e95fba7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openscreen", "private": true, - "version": "1.10.0-subscription.7", + "version": "1.10.0-subscription.11", "description": "Record your screen and polish the demo", "homepage": "https://getopenscreen.com/", "license": "MIT", diff --git a/public/purpleprint-studio.png b/public/purpleprint-studio.png new file mode 100644 index 000000000..e05e8ceab Binary files /dev/null and b/public/purpleprint-studio.png differ diff --git a/public/purpleprint-studio.svg b/public/purpleprint-studio.svg new file mode 100644 index 000000000..509fc4070 --- /dev/null +++ b/public/purpleprint-studio.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/fetch-ffmpeg.mjs b/scripts/fetch-ffmpeg.mjs index 8c2f0ed83..76709d659 100644 --- a/scripts/fetch-ffmpeg.mjs +++ b/scripts/fetch-ffmpeg.mjs @@ -315,27 +315,95 @@ function ffmpegSdkDest() { */ function vendorFfmpegSdk(tmp, dest) { // BtbN nests bin/ include/ lib/ under one versioned dir; find it by its headers. + console.log(`Locating FFmpeg SDK in verified archive for ${dest}`); const root = findDirContaining(tmp, "include"); if (!root) { throw new Error( "No include/ directory inside the shared archive — cannot vendor the ffmpeg SDK.", ); } - // A dev machine has this as a junction; rm unlinks it rather than eating the - // target, and we only get here on --force or when it is genuinely absent. - fs.rmSync(dest, { recursive: true, force: true }); + // Never write through a developer junction or a path outside this checkout. + // The destination name contains the pinned FFmpeg version, so an interrupted + // copy can be completed in place without deleting the whole SDK directory. + const thirdpartyPath = path.join(ROOT, "crates", "thirdparty"); + fs.mkdirSync(thirdpartyPath, { recursive: true }); + const thirdparty = fs.realpathSync.native(thirdpartyPath); + if (fs.lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink()) { + throw new Error(`Refusing to write FFmpeg SDK through link: ${dest}`); + } + const resolvedDest = fs.existsSync(dest) ? fs.realpathSync.native(dest) : path.resolve(dest); + if (!samePath(path.dirname(resolvedDest), thirdparty)) { + throw new Error(`Refusing to replace FFmpeg SDK outside ${thirdparty}: ${resolvedDest}`); + } fs.mkdirSync(path.dirname(dest), { recursive: true }); - // `verbatimSymlinks` matters on Linux, where BtbN ships lib/libavcodec.so -> - // libavcodec.so.62.28.102. WITHOUT it, cpSync RESOLVES each link and writes an - // absolute one pointing back into the extraction temp dir — which this function's - // caller deletes immediately after, leaving every dev symlink dangling. The - // headers then satisfy build.rs's assert while `-lavcodec` fails at link time, - // which is exactly how this presented. Windows has no symlinks here, so the flag - // is a no-op there. - fs.cpSync(root, dest, { recursive: true, verbatimSymlinks: true }); + console.log(`Copying verified FFmpeg SDK from ${root}`); + // Copy only build inputs, not the archive's executable and documentation. Walk + // files explicitly: Windows can terminate a bulk cpSync of this extracted tree + // without surfacing an exception, leaving an empty SDK directory that a simple + // existsSync check incorrectly treats as complete on the next run. + fs.mkdirSync(dest, { recursive: true }); + for (const part of ["include", "lib"]) { + const source = path.join(root, part); + if (!fs.existsSync(source)) throw new Error(`FFmpeg SDK archive missing ${part}/`); + copySdkTree(source, path.join(dest, part)); + } + console.log(`FFmpeg SDK copy finished: ${dest}`); + if (!fs.existsSync(path.join(dest, "include", "libavformat", "avformat.h"))) { + throw new Error(`FFmpeg SDK copy incomplete: ${dest}`); + } console.log(`Vendored ffmpeg SDK (include/ + lib/) -> ${dest}`); } +function copySdkTree(source, dest) { + if (fs.lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink()) { + throw new Error(`Refusing to write FFmpeg SDK through link: ${dest}`); + } + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(source, { withFileTypes: true })) { + const from = path.join(source, entry.name); + const to = path.join(dest, entry.name); + if (entry.isDirectory()) { + copySdkTree(from, to); + } else if (entry.isSymbolicLink()) { + // Preserve Linux's relative libav*.so links; never point at the temporary + // extraction tree, which disappears after staging. + const link = fs.readlinkSync(from); + if (fs.existsSync(to) || fs.lstatSync(to, { throwIfNoEntry: false })) { + if (fs.lstatSync(to).isSymbolicLink() && fs.readlinkSync(to) === link) continue; + throw new Error(`Refusing to replace a different FFmpeg SDK link: ${to}`); + } + fs.symlinkSync(link, to); + } else if (entry.isFile()) { + if (fs.lstatSync(to, { throwIfNoEntry: false })?.isSymbolicLink()) { + throw new Error(`Refusing to overwrite FFmpeg SDK link with file: ${to}`); + } + fs.copyFileSync(from, to); + } else { + throw new Error(`Unexpected FFmpeg SDK member: ${from}`); + } + } +} + +function samePath(left, right) { + return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right; +} + +function cleanupFfmpegExtraction(tmp) { + const tempRoot = fs.realpathSync.native(os.tmpdir()); + const resolved = fs.realpathSync.native(tmp); + if ( + !samePath(path.dirname(resolved), tempRoot) || + !path.basename(resolved).startsWith("openscreen-ffmpeg-") + ) { + throw new Error(`Refusing to remove unexpected FFmpeg extraction: ${resolved}`); + } + if (process.env.OPENSCREEN_KEEP_FFMPEG_TMP === "1") { + console.log(`Keeping FFmpeg extraction for diagnosis: ${tmp}`); + return; + } + fs.rmSync(tmp, { recursive: true, force: true }); +} + /** First directory at or under `dir` that has a child named `name`. */ function findDirContaining(dir, name) { if (fs.existsSync(path.join(dir, name))) return dir; @@ -418,7 +486,11 @@ async function fetchSharedDlls(tag, binDir) { // DLLs but not the SDK must still re-download — otherwise we skip here and // the compositor build fails afterwards on the missing FFMPEG_DIR. const sdkDest = ffmpegSdkDest(); - const sdkPresent = sdkDest == null || fs.existsSync(sdkDest); + const sdkPresent = + sdkDest == null || + (fs.existsSync(path.join(sdkDest, "include", "libavformat", "avformat.h")) && + fs.existsSync(path.join(sdkDest, "lib")) && + fs.readdirSync(path.join(sdkDest, "lib")).some((name) => /^(lib)?avformat/i.test(name))); if (alreadyVendored && sdkPresent && !process.argv.includes("--force")) { console.log( `\nShared ffmpeg libraries already present in ${binDir}. Use --force to re-vendor.`, @@ -490,7 +562,7 @@ async function fetchSharedDlls(tag, binDir) { if (sdkDest) vendorFfmpegSdk(tmp, sdkDest); console.log("LGPL verified: safe to ship with an MIT app."); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + cleanupFfmpegExtraction(tmp); } } @@ -563,7 +635,7 @@ async function main() { console.log(`\nVendored -> ${dest}`); console.log("LGPL verified: safe to ship with an MIT app."); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + cleanupFfmpegExtraction(tmp); } } diff --git a/scripts/generate-studio-icons.py b/scripts/generate-studio-icons.py new file mode 100644 index 000000000..be38e2cd4 --- /dev/null +++ b/scripts/generate-studio-icons.py @@ -0,0 +1,74 @@ +"""Regenerate PurplePrint Studio's checked-in app icons (requires Pillow). + +The geometry mirrors public/purpleprint-studio.svg. Run only when the mark +changes; normal application and release builds consume the checked-in files. +""" + +from pathlib import Path + +from PIL import Image, ImageDraw + + +ROOT = Path(__file__).resolve().parents[1] +ICON_ROOT = ROOT / "icons" / "icons" +PNG_SIZES = (16, 24, 32, 48, 64, 128, 256, 512, 1024) +MASTER_SIZE = 4096 + + +def points(coords: tuple[tuple[float, float], ...]) -> list[tuple[float, float]]: + scale = MASTER_SIZE / 64 + return [(x * scale, y * scale) for x, y in coords] + + +def render_master() -> Image.Image: + image = Image.new("RGBA", (MASTER_SIZE, MASTER_SIZE), (0, 0, 0, 0)) + draw = ImageDraw.Draw(image) + draw.rounded_rectangle( + (0, 0, MASTER_SIZE - 1, MASTER_SIZE - 1), + radius=15 * MASTER_SIZE / 64, + fill="#8B5CF6", + ) + draw.polygon( + points( + ( + (29.5, 11.5), (33.8, 25), (47.5, 29.3), (33.8, 33.7), + (29.5, 47.3), (25.1, 33.7), (11.5, 29.3), (25.1, 25), + ) + ), + fill="#FFFFFF", + ) + draw.polygon( + points( + ( + (47.5, 39), (49.2, 44.3), (54.5, 46), (49.2, 47.7), + (47.5, 53), (45.8, 47.7), (40.5, 46), (45.8, 44.3), + ) + ), + fill=(255, 255, 255, 230), + ) + return image + + +def main() -> None: + master = render_master() + png_dir = ICON_ROOT / "png" + png_dir.mkdir(parents=True, exist_ok=True) + for size in PNG_SIZES: + master.resize((size, size), Image.Resampling.LANCZOS).save( + png_dir / f"{size}x{size}.png", format="PNG" + ) + + icon_1024 = master.resize((1024, 1024), Image.Resampling.LANCZOS) + win_dir = ICON_ROOT / "win" + win_dir.mkdir(parents=True, exist_ok=True) + icon_1024.save( + win_dir / "icon.ico", format="ICO", sizes=[(s, s) for s in (16, 24, 32, 48, 64, 128, 256)] + ) + + mac_dir = ICON_ROOT / "mac" + mac_dir.mkdir(parents=True, exist_ok=True) + icon_1024.save(mac_dir / "icon.icns", format="ICNS") + + +if __name__ == "__main__": + main() diff --git a/scripts/validate-fork-windows-package.mjs b/scripts/validate-fork-windows-package.mjs new file mode 100644 index 000000000..987508b63 --- /dev/null +++ b/scripts/validate-fork-windows-package.mjs @@ -0,0 +1,142 @@ +import { createHash } from "node:crypto"; +import { + createReadStream, + existsSync, + readdirSync, + readFileSync, + statSync, + writeFileSync, +} from "node:fs"; +import path from "node:path"; +import { extractFile, listPackage } from "@electron/asar"; + +const root = path.resolve(import.meta.dirname, ".."); +const { version } = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")); +const release = path.join(root, "release", version); +const unpacked = path.join(release, "win-unpacked"); +const resources = path.join(unpacked, "resources"); +const installerName = `PurplePrint-Studio-${version}-Windows-x64-Setup.exe`; +const installer = path.join(release, installerName); +const blockmap = `${installer}.blockmap`; +const feed = path.join(release, "latest.yml"); +const asar = path.join(resources, "app.asar"); +const nativeDir = path.join(resources, "electron", "native", "bin", "win32-x64"); + +function requireFile(file) { + if (!existsSync(file) || !statSync(file).isFile()) { + throw new Error(`Missing packaged file: ${path.relative(root, file)}`); + } + return file; +} + +async function sha256(file) { + const hash = createHash("sha256"); + for await (const chunk of createReadStream(requireFile(file))) hash.update(chunk); + return hash.digest("hex"); +} + +for (const file of [ + installer, + blockmap, + feed, + path.join(unpacked, "PurplePrint Studio.exe"), + asar, + ...[ + "LICENSE", + "THIRD-PARTY-NOTICES.md", + "FORK.md", + "FORK-CHANGELOG.md", + "FORK-UPSTREAM-REVIEW.md", + ].map((name) => path.join(resources, name)), + ...[ + "compositor_view.node", + "wgc-capture.exe", + "cursor-sampler.exe", + "whisper-stt-server.exe", + ].map((name) => path.join(nativeDir, name)), +]) { + requireFile(file); +} + +for (const family of ["avcodec", "avformat", "avutil"]) { + if (!readdirSync(nativeDir).some((name) => new RegExp(`^${family}-\\d+\\.dll$`).test(name))) { + throw new Error(`Missing packaged ${family} DLL`); + } +} + +const updateConfig = readFileSync(path.join(resources, "app-update.yml"), "utf8"); +for (const line of ["owner: PurplePrintAI", "repo: openscreen-subscription", "provider: github"]) { + if (!updateConfig.split(/\r?\n/).includes(line)) + throw new Error(`Unexpected update feed: ${line}`); +} +const latestYaml = readFileSync(feed, "utf8"); +if (!latestYaml.includes(`version: ${version}`) || !latestYaml.includes(installerName)) { + throw new Error("Windows update feed does not match the installer version/name"); +} + +const packagedFiles = new Set( + listPackage(asar).map((name) => name.replaceAll("\\", "/").replace(/^\/+/, "")), +); +if (!packagedFiles.has("dist/wasm/web-demuxer.wasm")) { + throw new Error("Packaged transcription WASM is missing"); +} +const packagedManifest = JSON.parse(extractFile(asar, "package.json").toString()); +if (packagedManifest.version !== version || packagedManifest.name !== "openscreen-subscription") { + throw new Error("Packaged version or technical identity is wrong"); +} +const transcribeChunk = [...packagedFiles].find((name) => + /^dist\/assets\/transcribe-[^/]+\.js$/.test(name), +); +if (!transcribeChunk) throw new Error("Packaged transcription chunk is missing"); +const source = extractFile(asar, transcribeChunk.replaceAll("/", "\\")).toString(); +if ( + !source.includes("./wasm/web-demuxer.wasm") || + source.includes("../exporter/wasm/web-demuxer.wasm") +) { + throw new Error("Packaged transcription WASM URL is incorrect"); +} + +const compositor = path.join(nativeDir, "compositor_view.node"); +const buildOutputCompositor = path.join(root, "crates", "target", "release", "compositor_view.dll"); +if ((await sha256(compositor)) !== (await sha256(buildOutputCompositor))) { + throw new Error("Packaged compositor differs from the native build output"); +} +const wgc = path.join(nativeDir, "wgc-capture.exe"); +if ( + (await sha256(wgc)) !== + (await sha256(path.join(root, "electron", "native", "bin", "win32-x64", "wgc-capture.exe"))) +) { + throw new Error("Packaged WGC helper differs from the native build output"); +} + +const installerSha = await sha256(installer); +const blockmapSha = await sha256(blockmap); +const feedSha = await sha256(feed); +const validation = { + product: "PurplePrint Studio", + version, + architecture: "x64", + appId: "io.github.purpleprintai.openscreen-subscription", + sourceCommit: process.env.GITHUB_SHA ?? null, + prHeadCommit: process.env.GITHUB_HEAD_SHA || null, + checks: { + forkUpdateFeed: true, + packageIdentity: true, + attribution: true, + nativePayloadPresent: true, + compositorMatchesBuildOutput: true, + wgcMatchesFreshBuild: true, + transcriptionWasm: true, + windowsUpdateFeed: true, + }, + installer: { name: installerName, bytes: statSync(installer).size, sha256: installerSha }, +}; +writeFileSync( + path.join(release, "windows-validation.json"), + `${JSON.stringify(validation, null, 2)}\n`, +); +writeFileSync( + path.join(release, "SHA256SUMS-Windows.txt"), + `${installerSha} ${installerName}\n${blockmapSha} ${path.basename(blockmap)}\n${feedSha} latest.yml\n`, +); +console.log(`Validated PurplePrint Studio ${version} Windows x64 package`); diff --git a/src/App.tsx b/src/App.tsx index aa7619d26..7369ef015 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -154,7 +154,7 @@ export default function App() { return (
-

Openscreen

+

PurplePrint Studio

); diff --git a/src/components/ai-edition/AnnotationOverlay.tsx b/src/components/ai-edition/AnnotationOverlay.tsx index 81f8c2592..5c7685608 100644 --- a/src/components/ai-edition/AnnotationOverlay.tsx +++ b/src/components/ai-edition/AnnotationOverlay.tsx @@ -132,17 +132,16 @@ export function AnnotationOverlay({ "cursor-move", isSelected && annotation.type !== "blur" && - "ring-2 ring-[#34B27B] ring-offset-2 ring-offset-transparent", + "ring-2 ring-[var(--accent)] ring-offset-2 ring-offset-transparent", )} style={{ zIndex: isSelectedBoost ? zIndex + 1000 : zIndex, pointerEvents: isSelected ? "auto" : "none", - border: - isSelected && annotation.type !== "blur" ? "2px solid rgba(52, 178, 123, 0.8)" : "none", + border: isSelected && annotation.type !== "blur" ? "2px solid var(--accent)" : "none", backgroundColor: - isSelected && annotation.type !== "blur" ? "rgba(52, 178, 123, 0.1)" : "transparent", + isSelected && annotation.type !== "blur" ? "var(--accent-soft)" : "transparent", boxShadow: - isSelected && annotation.type !== "blur" ? "0 0 0 1px rgba(52, 178, 123, 0.35)" : "none", + isSelected && annotation.type !== "blur" ? "0 0 0 1px var(--accent-ring)" : "none", }} // Un flou en tracé libre se déplace et se redimensionne comme les autres : ce qui le // bloquait, c'était la zone de saisie du tracé qui capturait le pointeur — et elle est @@ -154,7 +153,7 @@ export function AnnotationOverlay({ width: "12px", height: "12px", backgroundColor: isSelected ? "white" : "transparent", - border: isSelected ? "2px solid #34B27B" : "none", + border: isSelected ? "2px solid var(--accent)" : "none", borderRadius: "50%", left: "-6px", top: "-6px", @@ -164,7 +163,7 @@ export function AnnotationOverlay({ width: "12px", height: "12px", backgroundColor: isSelected ? "white" : "transparent", - border: isSelected ? "2px solid #34B27B" : "none", + border: isSelected ? "2px solid var(--accent)" : "none", borderRadius: "50%", right: "-6px", top: "-6px", @@ -174,7 +173,7 @@ export function AnnotationOverlay({ width: "12px", height: "12px", backgroundColor: isSelected ? "white" : "transparent", - border: isSelected ? "2px solid #34B27B" : "none", + border: isSelected ? "2px solid var(--accent)" : "none", borderRadius: "50%", left: "-6px", bottom: "-6px", @@ -184,7 +183,7 @@ export function AnnotationOverlay({ width: "12px", height: "12px", backgroundColor: isSelected ? "white" : "transparent", - border: isSelected ? "2px solid #34B27B" : "none", + border: isSelected ? "2px solid var(--accent)" : "none", borderRadius: "50%", right: "-6px", bottom: "-6px", @@ -204,7 +203,7 @@ export function AnnotationOverlay({ est censé cacher — sans ce liseré il n'aurait AUCUN retour de sélection. */} {isSelected && annotation.type === "blur" ? (
) : null} diff --git a/src/components/ai-edition/EditorEmptyState.tsx b/src/components/ai-edition/EditorEmptyState.tsx index 226171dca..9e2f7a399 100644 --- a/src/components/ai-edition/EditorEmptyState.tsx +++ b/src/components/ai-edition/EditorEmptyState.tsx @@ -10,7 +10,7 @@ // deprecated editor and its own `EditorEmptyState` were deleted in 1320121d, // so this is the single render path for the feature. -import { AlertCircle, Film, FolderOpen, Upload, X } from "lucide-react"; +import { AlertCircle, Film, FolderOpen, Sparkles, Upload, X } from "lucide-react"; import { useCallback, useRef, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { useScopedT } from "@/contexts/I18nContext"; @@ -174,7 +174,7 @@ export function EditorEmptyState({
- +

{hasProject ? t("emptyState.titleHasAsset") : t("emptyState.title")} diff --git a/src/components/ai-edition/ExportDialog.tsx b/src/components/ai-edition/ExportDialog.tsx index c661933f1..b5f13ef0b 100644 --- a/src/components/ai-edition/ExportDialog.tsx +++ b/src/components/ai-edition/ExportDialog.tsx @@ -249,7 +249,7 @@ export function ExportDialog({ open, onClose, document }: ExportDialogProps) { return; } - const safeName = (document.project.title || "OpenScreen") + const safeName = (document.project.title || "PurplePrint Studio") .replace(/[^a-z0-9-_]+/gi, "_") .replace(/^_+|_+$/g, "") .slice(0, 60); diff --git a/src/components/ai-edition/NewEditorShell.module.css b/src/components/ai-edition/NewEditorShell.module.css index 90a4568d6..1c0e754e4 100644 --- a/src/components/ai-edition/NewEditorShell.module.css +++ b/src/components/ai-edition/NewEditorShell.module.css @@ -532,20 +532,20 @@ align-items: center; justify-content: center; gap: var(--sp-2); - border: 2px dashed var(--primary, #34B27B); - background: color-mix(in srgb, var(--primary, #34B27B) 10%, transparent); + border: 2px dashed var(--primary, #8B5CF6); + background: color-mix(in srgb, var(--primary, #8B5CF6) 10%, transparent); border-radius: var(--r-sm); pointer-events: none; } .previewEmptyDropIcon { width: 40px; height: 40px; - color: var(--primary, #34B27B); + color: var(--primary, #8B5CF6); } .previewEmptyDropLabel { font-size: var(--fs-app); font-weight: 600; - color: var(--primary, #34B27B); + color: var(--primary, #8B5CF6); } .previewEmptyInner { display: flex; @@ -559,8 +559,12 @@ .previewEmptyLogo { width: 64px; height: 64px; - border-radius: var(--r-md); - opacity: 0.9; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--r-lg); + background: var(--accent); + color: #fff; } .previewEmptyHeading { display: flex; @@ -593,7 +597,7 @@ gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm); - background: var(--primary, #34B27B); + background: var(--primary, #8B5CF6); color: var(--accent-on); border: none; font-weight: 500; @@ -2458,7 +2462,7 @@ color: var(--fg-2); } .chatWelcomeFeatures li + li { margin-top: 6px; } -.chatWelcomeFeatures li::marker { color: var(--accent); } +.chatWelcomeFeatures li::marker { color: var(--muted); } .chatWelcomeCta { display: inline-flex; @@ -2468,14 +2472,14 @@ width: 100%; height: 34px; border-radius: var(--r-md); - background: var(--accent); - color: var(--accent-on); - border: 1px solid var(--accent); + background: var(--surface-2); + color: var(--fg-2); + border: 1px solid var(--border-hi); font: 600 12.5px var(--font-body); cursor: pointer; - transition: filter 120ms ease; + transition: background-color 120ms ease; } -.chatWelcomeCta:hover { filter: brightness(0.95); } +.chatWelcomeCta:hover { background: var(--surface-3); } .chatWelcomeCta:focus-visible { outline: none; box-shadow: var(--focus-ring); diff --git a/src/components/ai-edition/NewEditorShell.tsx b/src/components/ai-edition/NewEditorShell.tsx index 64987dea9..943312844 100644 --- a/src/components/ai-edition/NewEditorShell.tsx +++ b/src/components/ai-edition/NewEditorShell.tsx @@ -110,14 +110,26 @@ export function NewEditorShell() { // v4 shell: three modes (Media / Edit / Rec), a collapsible agent (chat) // column, and a floating facet inspector over the stage. const [mode, setMode] = useState("edit"); - const [chatOpen, setChatOpen] = useState(true); + const [chatOpen, setChatOpen] = useState(() => window.innerWidth >= 900); const [chatWidthPx, setChatWidthPx] = useState( () => Number(localStorage.getItem("os-editor-chat-width")) || 392, ); - const [timelineHeightPx, setTimelineHeightPx] = useState( - () => Number(localStorage.getItem("os-editor-timeline-height")) || 308, + const [timelineHeightPx, setTimelineHeightPx] = useState(() => + Math.min( + Number(localStorage.getItem("os-editor-timeline-height")) || 308, + window.innerHeight < 700 ? 220 : 480, + ), ); - const [inspectorOpen, setInspectorOpen] = useState(true); + const [inspectorOpen, setInspectorOpen] = useState(() => window.innerWidth >= 1100); + useEffect(() => { + const collapseWhenNarrow = () => { + if (window.innerWidth < 900) setChatOpen(false); + if (window.innerWidth < 1100) setInspectorOpen(false); + if (window.innerHeight < 700) setTimelineHeightPx((height) => Math.min(height, 220)); + }; + window.addEventListener("resize", collapseWhenNarrow); + return () => window.removeEventListener("resize", collapseWhenNarrow); + }, []); const [facet, setFacet] = useState("effects"); const [openProjectOpen, setOpenProjectOpen] = useState(false); const [newProjectOpen, setNewProjectOpen] = useState(false); diff --git a/src/components/ai-edition/ProviderSettings.test.tsx b/src/components/ai-edition/ProviderSettings.test.tsx index ba7a1769c..447dad41e 100644 --- a/src/components/ai-edition/ProviderSettings.test.tsx +++ b/src/components/ai-edition/ProviderSettings.test.tsx @@ -97,7 +97,7 @@ function renderEditorChrome(locale: string, mode: EditorMode = "edit") { /** Open the app menu (the wordmark) and click its AI settings row. */ function openAiSettingsFromAppMenu() { - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); fireEvent.click(screen.getByRole("menuitem", { name: /ai settings/i })); } @@ -324,7 +324,7 @@ describe("ProviderSettings, reached from the app menu", () => { // something else after a copy edit, and the menu would start lying about where it goes. // Compared as text rather than asserted against a literal, so a copy edit moves both. renderEditorChrome("en"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); const rowLabel = screen.getByRole("menuitem", { name: /ai settings/i }).textContent; fireEvent.click(screen.getByRole("menuitem", { name: /ai settings/i })); @@ -343,7 +343,7 @@ describe("ProviderSettings, reached from the app menu", () => { it("translates the row with the dialog, not separately", () => { renderEditorChrome("fr"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); const row = screen.getByRole("menuitem", { name: /paramètres ia/i }); fireEvent.click(row); diff --git a/src/components/ai-edition/TranscriptionStatus.tsx b/src/components/ai-edition/TranscriptionStatus.tsx index bae6a0976..ca5f52bac 100644 --- a/src/components/ai-edition/TranscriptionStatus.tsx +++ b/src/components/ai-edition/TranscriptionStatus.tsx @@ -67,7 +67,7 @@ export function useTranscriptionLabel(): (view: AssetTranscriptionView) => strin const DOT_COLOR: Record = { ready: { fill: "var(--success)", halo: "0 0 0 3px var(--success-soft)" }, queued: { fill: "#f59e0b", halo: "0 0 0 3px rgba(245, 158, 11, 0.2)" }, - running: { fill: "var(--accent)", halo: "0 0 0 3px rgba(16, 185, 129, 0.2)" }, + running: { fill: "var(--warn)", halo: "0 0 0 3px var(--warn-soft)" }, // A silent media is not a bug — it just has nothing to say. Amber, not red. empty: { fill: "#f59e0b", halo: "0 0 0 3px rgba(245, 158, 11, 0.2)" }, failed: { fill: "var(--danger)", halo: "0 0 0 3px rgba(239, 68, 68, 0.2)" }, diff --git a/src/components/ai-edition/ZoomFocusOverlay.module.css b/src/components/ai-edition/ZoomFocusOverlay.module.css index f0f549bfc..92eb264fb 100644 --- a/src/components/ai-edition/ZoomFocusOverlay.module.css +++ b/src/components/ai-edition/ZoomFocusOverlay.module.css @@ -8,8 +8,8 @@ .indicator { position: absolute; - border: 1px solid rgba(52, 178, 123, 0.8); - background: rgba(52, 178, 123, 0.2); + border: 1px solid var(--accent); + background: var(--accent-soft); border-radius: 6px; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); } diff --git a/src/components/ai-edition/v4/EditorShellV4.module.css b/src/components/ai-edition/v4/EditorShellV4.module.css index 1e313e34c..272039e06 100644 --- a/src/components/ai-edition/v4/EditorShellV4.module.css +++ b/src/components/ai-edition/v4/EditorShellV4.module.css @@ -1,5 +1,5 @@ -/* OpenScreen Editor v4 shell — Media / Edit / Rec modes. - * Ported 1:1 from design/OpenScreen Editor v4.dc.html. All colors read the +/* PurplePrint Studio editor shell — Media / Edit / Rec modes. + * Based on design/OpenScreen Editor v4.dc.html. All colors read the * global --* design tokens (src/styles/design-tokens.css); dark is the hero * look, light is a full token swap handled by the token layer. */ @@ -83,22 +83,31 @@ gap: 9px; flex-shrink: 0; } -/* Transparent aperture mark: no plate behind it, so it needs a touch more box - than a filled tile would to hold the same optical weight next to the wordmark. */ -.brand img { +/* GraphOS family tile: neutral chrome, violet only at the product identity. */ +.brandGlyph { width: 24px; height: 24px; - object-fit: contain; - display: block; + display: inline-flex; + align-items: center; + justify-content: center; flex-shrink: 0; - user-select: none; + border-radius: 6px; + background: var(--accent); + color: #fff; } .brand .name { + display: inline-flex; + align-items: baseline; + gap: 5px; font-size: 15px; font-weight: 600; letter-spacing: -0.015em; color: var(--fg-emphasis); } +.productName { + font-weight: 500; + color: var(--fg); +} /* The brand doubles as the app-menu trigger, so it has to read as a button on hover without gaining any width: .modeSwitch is the only flex-shrink:1 element in the bar, so every pixel a new control costs is paid out of the mode labels in the most verbose of 13 locales. The @@ -298,8 +307,8 @@ width: 6px; height: 6px; border-radius: 50%; - background: var(--accent); - box-shadow: 0 0 0 3px var(--accent-soft); + background: var(--success); + box-shadow: 0 0 0 3px var(--success-soft); } /* One box for every short locale label — see --topbar-lang-label-w. Without it @@ -402,7 +411,10 @@ background: var(--brand-lo); } .exportBtn:disabled { - opacity: 0.5; + background: var(--surface-2); + border-color: var(--border); + color: var(--muted); + box-shadow: none; cursor: not-allowed; } @@ -1756,3 +1768,34 @@ pointer-events: none; z-index: 14; } + +/* Desktop windows can be narrowed to 800px. Keep the editor modes and export + reachable without moving core actions into an undiscoverable overflow menu. */ +@media (max-width: 1100px) { + .topbar { + --topbar-gap: 8px; + --topbar-project-w: 110px; + padding-left: calc(10px + var(--titlebar-inset-left, 0px)); + padding-right: calc(10px + 100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px)); + } + .familyName { + display: none; + } + .modeSwitch button { + padding-inline: 8px; + } +} +@media (max-width: 900px) { + .topbar { + --topbar-gap: 5px; + --topbar-project-w: 80px; + --topbar-lang-label-w: 26px; + } + .brand .name, + .saved { + display: none; + } + .modeSwitch button { + padding-inline: 5px; + } +} diff --git a/src/components/ai-edition/v4/EditorTopBar.test.tsx b/src/components/ai-edition/v4/EditorTopBar.test.tsx index 0b3b40db1..3bec66615 100644 --- a/src/components/ai-edition/v4/EditorTopBar.test.tsx +++ b/src/components/ai-edition/v4/EditorTopBar.test.tsx @@ -145,7 +145,7 @@ describe("ProjectNameField (issue #180)", () => { describe("AppMenu", () => { it("hangs the menu on the brand rather than adding a control to the bar", () => { renderTopBar("Demo Project"); - const trigger = screen.getByRole("button", { name: /OpenScreen/ }); + const trigger = screen.getByRole("button", { name: /PurplePrint Studio/ }); expect(trigger).toHaveAttribute("aria-haspopup", "menu"); expect(trigger).toHaveAttribute("aria-expanded", "false"); // The whole point of the wordmark-as-trigger: no menu until asked for. @@ -158,14 +158,14 @@ describe("AppMenu", () => { // on the window-drag region and the OS eats the click — the #180 failure, one control // over. Same reason `all: unset` is banned here. renderTopBar("Demo Project"); - const trigger = screen.getByRole("button", { name: /OpenScreen/ }); + const trigger = screen.getByRole("button", { name: /PurplePrint Studio/ }); expect(trigger.tagName).toBe("BUTTON"); expect(trigger.getAttribute("style") ?? "").not.toMatch(/all\s*:\s*unset/); }); it("opens on click and offers shortcuts, AI settings and about", () => { renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); expect(screen.getByRole("menu")).toBeInTheDocument(); // Exact names: the translator echoes keys, and both settings rows are labelled with a // `…title` key, so a /title/ match would hit two items and pin neither. @@ -179,7 +179,7 @@ describe("AppMenu", () => { // in App.tsx, above every mode — so the menu does not lie in Media and Rec. it("opens the AI settings dialog and closes behind itself", () => { const { onOpenProviderSettings, onOpenSettings } = renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); fireEvent.click(screen.getByRole("menuitem", { name: "providerSettings.title" })); expect(onOpenProviderSettings).toHaveBeenCalledTimes(1); // Distinct from the shortcuts row above it, which is the dialog it would be confused with. @@ -189,7 +189,7 @@ describe("AppMenu", () => { it("routes About to the main process and closes behind itself", () => { const { onShowAbout } = renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); fireEvent.click(screen.getByRole("menuitem", { name: /actions\.about/ })); expect(onShowAbout).toHaveBeenCalledTimes(1); expect(screen.queryByRole("menu")).not.toBeInTheDocument(); @@ -197,7 +197,7 @@ describe("AppMenu", () => { it("closes on Escape", () => { renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); fireEvent.keyDown(screen.getByRole("menu"), { key: "Escape" }); expect(screen.queryByRole("menu")).not.toBeInTheDocument(); }); @@ -207,7 +207,7 @@ describe("AppMenu", () => { // build answering false, and as a check refused mid-take: no item, rather than a button // that silently does nothing. renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); expect( screen.queryByRole("menuitem", { name: /actions\.checkForUpdates/ }), ).not.toBeInTheDocument(); @@ -217,7 +217,7 @@ describe("AppMenu", () => { const restore = stubElectronAPI({ version: "9.9.9", canCheckForUpdates: true }); try { const { onCheckForUpdates } = renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); const item = await screen.findByRole("menuitem", { name: /actions\.checkForUpdates/ }); fireEvent.click(item); expect(onCheckForUpdates).toHaveBeenCalledTimes(1); @@ -230,7 +230,7 @@ describe("AppMenu", () => { const restore = stubElectronAPI({ version: "9.9.9", canCheckForUpdates: false }); try { renderTopBar("Demo Project"); - fireEvent.click(screen.getByRole("button", { name: /OpenScreen/ })); + fireEvent.click(screen.getByRole("button", { name: /PurplePrint Studio/ })); expect(await screen.findByText("9.9.9")).toBeInTheDocument(); } finally { restore(); diff --git a/src/components/ai-edition/v4/EditorTopBar.tsx b/src/components/ai-edition/v4/EditorTopBar.tsx index 58de1288c..02f06b8e0 100644 --- a/src/components/ai-edition/v4/EditorTopBar.tsx +++ b/src/components/ai-edition/v4/EditorTopBar.tsx @@ -14,7 +14,6 @@ import { Sun, } from "lucide-react"; import { type KeyboardEvent as ReactKeyboardEvent, useEffect, useRef, useState } from "react"; -import logoMark from "@/assets/openscreen-mark.png"; import { useI18n, useScopedT } from "@/contexts/I18nContext"; import { useTheme } from "@/hooks/useTheme"; import { getAvailableLocales, getLocaleName, getLocaleShort } from "@/i18n/loader"; @@ -382,14 +381,20 @@ function AppMenu({ actions }: { actions: TopBarActions }) { ref={triggerRef} type="button" className={`${styles.brand} ${styles.brandBtn}`} + aria-label="PurplePrint Studio" + title="PurplePrint Studio" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((v) => !v)} > - {/* Decorative: the wordmark beside it already names the app — and, being the - button's only text, is also its accessible name. */} - - OpenScreen Subscription + {/* The GraphOS-family brand tile is decorative; aria-label names the menu. */} + + + PurplePrint + Studio + {open ? ( diff --git a/src/components/launch/HudControls.tsx b/src/components/launch/HudControls.tsx index 360499c79..0dccc0599 100644 --- a/src/components/launch/HudControls.tsx +++ b/src/components/launch/HudControls.tsx @@ -269,7 +269,7 @@ export const HudCursorButton = memo(function HudCursorButton({ data-testid="launch-cursor-mode-button" className={`flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-[10px] border-0 cursor-pointer transition-all duration-150 active:scale-95 ${hudDisabledClasses} ${styles.electronNoDrag} ${ editableOverlay - ? "bg-[#10b981] text-[#08090d] hover:bg-[#0e9e6e]" + ? "bg-[#8b5cf6] text-white hover:bg-[#7c3aed]" : "bg-transparent text-[#828c99] hover:bg-[#1a1e25] hover:text-[#f5f7fa]" }`} onClick={onClick} @@ -285,6 +285,7 @@ export const HudRecordButton = memo(function HudRecordButton({ recording, paused, saving, + starting, elapsedSeconds, label, savingLabel, @@ -293,18 +294,20 @@ export const HudRecordButton = memo(function HudRecordButton({ recording: boolean; paused: boolean; saving: boolean; + starting: boolean; elapsedSeconds: number; label: string; savingLabel: string; onClick: () => void; }) { + const busy = saving || starting; return ( @@ -190,7 +190,7 @@ export function SourceSelector() { data-testid="source-selector-share-button" onClick={handleShare} disabled={!selectedSource} - className="h-9 rounded-[9px] bg-[#10b981] px-5 text-[13px] font-semibold text-[#08090d] transition-transform duration-150 hover:bg-[#10b981]/85 active:scale-95 disabled:bg-[#232830] disabled:border disabled:border-[#242932] disabled:text-[#565f6b] disabled:opacity-100" + className="h-9 rounded-[9px] bg-[#8b5cf6] px-5 text-[13px] font-semibold text-white transition-transform duration-150 hover:bg-[#7c3aed] active:scale-95 disabled:bg-[#262626] disabled:border disabled:border-[#333333] disabled:text-[#71717a] disabled:opacity-100" > {tc("actions.share")} diff --git a/src/hooks/useScreenRecorder.nativeStopFailure.test.tsx b/src/hooks/useScreenRecorder.nativeStopFailure.test.tsx index 928ac5886..91fc48a49 100644 --- a/src/hooks/useScreenRecorder.nativeStopFailure.test.tsx +++ b/src/hooks/useScreenRecorder.nativeStopFailure.test.tsx @@ -80,6 +80,41 @@ afterEach(() => { }); describe("useScreenRecorder native Windows stop failure", () => { + it("ignores a second Record click while the native helper is still starting", async () => { + let confirmStart: ((value: { success: true; recordingId: number }) => void) | undefined; + api.startNativeWindowsRecording.mockImplementation( + () => + new Promise((resolve) => { + confirmStart = resolve; + }), + ); + + const view = renderHook(() => useScreenRecorder()); + await act(async () => { + view.result.current.toggleRecording(); + }); + await settle(3_500); + expect(api.startNativeWindowsRecording).toHaveBeenCalledTimes(1); + expect(view.result.current.starting).toBe(true); + + await act(async () => { + view.result.current.toggleRecording(); + }); + await settle(3_500); + expect(api.showCountdownOverlay).toHaveBeenCalledTimes(1); + expect(api.startNativeWindowsRecording).toHaveBeenCalledTimes(1); + + const finishStart = confirmStart; + if (!finishStart) throw new Error("Native start was not requested"); + await act(async () => { + finishStart({ success: true, recordingId: 7 }); + }); + await settle(); + expect(view.result.current.recording).toBe(true); + expect(view.result.current.starting).toBe(false); + expect(toast.error).not.toHaveBeenCalled(); + }); + /** * Issue #252's second symptom. When the helper wedges, the main process * releases its handle in a `finally` regardless, so a renderer that kept its diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 14eec5ab4..02ccccefe 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -57,6 +57,7 @@ type UseScreenRecorderReturn = { recording: boolean; paused: boolean; saving: boolean; + starting: boolean; elapsedSeconds: number; toggleRecording: () => void; /** Starts recording with no countdown overlay. Used by the headless CLI runner. */ @@ -208,6 +209,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const [recording, setRecording] = useState(false); const [paused, setPaused] = useState(false); const [saving, setSaving] = useState(false); + const [starting, setStarting] = useState(false); const [elapsedSeconds, setElapsedSeconds] = useState(0); const [microphoneEnabled, setMicrophoneEnabled] = useState(false); const [microphoneDeviceId, setMicrophoneDeviceId] = useState(undefined); @@ -271,6 +273,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const allowAutoFinalize = useRef(false); const discardRecordingId = useRef(null); const restarting = useRef(false); + // A native helper can take seconds to acknowledge Start. During that gap the + // countdown is gone but `recording` is still false; a second click must not + // issue another start IPC against the first helper process. + const startingRecording = useRef(false); const countdownRunId = useRef(0); const [countdownActive, setCountdownActive] = useState(false); const webcamReady = useRef(false); @@ -1509,7 +1515,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; const startRecordCountdown = async () => { - if (countdownActive || recording) { + if (countdownActive || recording || startingRecording.current) { return; } @@ -1654,6 +1660,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { countdownRunToken?: number, preparedRecordingId?: number | null, ) => { + if (startingRecording.current) return; + startingRecording.current = true; + setStarting(true); try { if (!isCountdownRunActive(countdownRunToken)) { teardownMedia(); @@ -1957,6 +1966,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { screenRecorder.current = null; webcamRecorder.current = null; teardownMedia(); + } finally { + startingRecording.current = false; + setStarting(false); } }; @@ -2114,6 +2126,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; const toggleRecording = () => { + if (startingRecording.current) return; if (recording) { stopRecording.current(); return; @@ -2276,6 +2289,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { recording, paused, saving, + starting, elapsedSeconds, toggleRecording, startRecordingImmediately: () => startRecording(), diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index f99994082..2aecda788 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -7,9 +7,9 @@ const STORAGE_KEY = "openscreen-theme"; function readStoredTheme(): Theme { try { const raw = localStorage.getItem(STORAGE_KEY); - return raw === "dark" ? "dark" : "light"; + return raw === "light" ? "light" : "dark"; } catch { - return "light"; + return "dark"; } } diff --git a/src/i18n/locales/ar/common.json b/src/i18n/locales/ar/common.json index b63cd0265..4ff68f7ca 100644 --- a/src/i18n/locales/ar/common.json +++ b/src/i18n/locales/ar/common.json @@ -36,19 +36,19 @@ "zoomOut": "تصغير", "toggleFullScreen": "ملء الشاشة", "recordingStatus": "جاري التسجيل: {{source}}", - "about": "حول OpenScreen", + "about": "حول PurplePrint Studio", "services": "خدمات", - "hide": "إخفاء OpenScreen", + "hide": "إخفاء PurplePrint Studio", "hideOthers": "إخفاء الآخرين", "unhide": "إظهار الكل", "saveDiagnostics": "حفظ التشخيصات" }, "updates": { - "available": "يتوفر OpenScreen {{latestVersion}}. الإصدار المثبت هو {{currentVersion}}.", - "current": "OpenScreen محدّث ({{currentVersion}}).", - "readyToInstall": "تم تنزيل OpenScreen {{latestVersion}}. أعد التشغيل لإكمال التثبيت.", + "available": "يتوفر PurplePrint Studio {{latestVersion}}. الإصدار المثبت هو {{currentVersion}}.", + "current": "PurplePrint Studio محدّث ({{currentVersion}}).", + "readyToInstall": "تم تنزيل PurplePrint Studio {{latestVersion}}. أعد التشغيل لإكمال التثبيت.", "blockedRecording": "أنهِ التسجيل قبل تثبيت التحديث.", - "blockedLocation": "انقل OpenScreen إلى مجلد التطبيقات قبل التحديث.", + "blockedLocation": "انقل PurplePrint Studio إلى مجلد التطبيقات قبل التحديث.", "failed": "تعذّر التحقق من وجود تحديثات.", "downloadFailed": "تعذّر تنزيل التحديث." }, diff --git a/src/i18n/locales/ar/dialogs.json b/src/i18n/locales/ar/dialogs.json index 43caf5938..98a1a3813 100644 --- a/src/i18n/locales/ar/dialogs.json +++ b/src/i18n/locales/ar/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "حفظ GIF المصدر", "saveVideo": "حفظ الفيديو المصدر", "selectVideo": "حدد ملف فيديو", - "saveProject": "حفظ مشروع OpenScreen", - "openProject": "فتح مشروع OpenScreen", + "saveProject": "حفظ مشروع PurplePrint Studio", + "openProject": "فتح مشروع PurplePrint Studio", "gifImage": "صورة GIF", "mp4Video": "فيديو MP4", "videoFiles": "ملفات فيديو", - "openscreenProject": "مشروع OpenScreen", + "openscreenProject": "مشروع PurplePrint Studio", "allFiles": "جميع الملفات" } } diff --git a/src/i18n/locales/ar/editor.json b/src/i18n/locales/ar/editor.json index 62959cf3e..e4bdff771 100644 --- a/src/i18n/locales/ar/editor.json +++ b/src/i18n/locales/ar/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "تعذّر فتح الكاميرا. يجري التسجيل بدون كاميرا.", "microphoneDefaulted": "تعذّر تحديد الميكروفون المحدَّد؛ يجري التسجيل من الإدخال الافتراضي.", "permissionDenied": "تم رفض إذن التسجيل. يرجى السماح بتسجيل الشاشة.", - "accessibilityAllowAndRetry": "اسمح بوصول تسهيلات الاستخدام لـ OpenScreen، ثم اضغط على التسجيل مرة أخرى لبدء العد التنازلي.", + "accessibilityAllowAndRetry": "اسمح بوصول تسهيلات الاستخدام لـ PurplePrint Studio، ثم اضغط على التسجيل مرة أخرى لبدء العد التنازلي.", "selectSource": "يرجى تحديد مصدر للتسجيل" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "سيُرسَل نصّ فيديوك إلى المزوّد الذي تختاره. لا يُشارَك شيء قبل أن تربط مزوّداً." }, "authorUser": "أنت", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "إعادة الضبط إلى هذه الرسالة", "copyMessage": "نسخ الرسالة", "copiedToClipboard": "تم النسخ إلى الحافظة", @@ -402,12 +402,12 @@ "enterCustomModel": "إدخال معرّف النموذج", "chooseCatalogModel": "الاختيار من الكتالوج", "pillLocalCli": "واجهة CLI محلية", - "localCliHint": "سجّل الدخول عبر واجهة Claude Code CLI الرسمية. تتولى المصادقة وتستخدم الاشتراك أو مفتاح API أو حساب المزوّد النشط. لا يخزّن OpenScreen بيانات الاعتماد هذه ولا يغيّرها.", + "localCliHint": "سجّل الدخول عبر واجهة Claude Code CLI الرسمية. تتولى المصادقة وتستخدم الاشتراك أو مفتاح API أو حساب المزوّد النشط. لا يخزّن PurplePrint Studio بيانات الاعتماد هذه ولا يغيّرها.", "cliAccountSubscription": "اشتراك Claude: {{plan}}. يتبع الوصول إلى النماذج والفوترة خطتك.", "cliAccountApi": "مصادقة API نشطة. تُطبّق رسوم API وليس حدود الاشتراك.", "cliAccountRuntime": "تمت مصادقة CLI. يتبع الاستخدام والفوترة إعداداتها الحالية.", "checkCliConnection": "التحقق من اتصال CLI", - "disconnectLocalCli": "إيقاف الاستخدام في OpenScreen", + "disconnectLocalCli": "إيقاف الاستخدام في PurplePrint Studio", "localCliDeselected": "تم إلغاء اختيار Claude. يبقى حساب CLI مسجّل الدخول.", "modelHintCli": "القائمة من واجهة Claude Code CLI الرسمية. يعتمد الوصول إلى النماذج والسياق على حسابك؛ قد تستخدم بعض الخيارات أرصدة الاستخدام.", "title": "إعدادات الذكاء الاصطناعي", @@ -442,7 +442,7 @@ "signInChatGPT": "تسجيل الدخول باستخدام ChatGPT", "signInPending": "أكمل تسجيل الدخول في المتصفح. يتم تحديث الحالة تلقائيًا.", "cancelSignIn": "إلغاء تسجيل الدخول", - "subscriptionHint": "يتطلب Codex CLI وخطة ChatGPT مؤهلة. يدير Codex المصادقة في ملف خاص بـ OpenScreen دون نسخ مفاتيح API أو الرموز. تسري حدود خطتك." + "subscriptionHint": "يتطلب Codex CLI وخطة ChatGPT مؤهلة. يدير Codex المصادقة في ملف خاص بـ PurplePrint Studio دون نسخ مفاتيح API أو الرموز. تسري حدود خطتك." }, "cpuCompositor": { "notice": "لا توجد بطاقة رسومات متوافقة — تتم المعالجة على المعالج، لذا يكون التشغيل أبطأ.", diff --git a/src/i18n/locales/ar/launch.json b/src/i18n/locales/ar/launch.json index 0198af382..db874ab4c 100644 --- a/src/i18n/locales/ar/launch.json +++ b/src/i18n/locales/ar/launch.json @@ -57,8 +57,8 @@ "screens": "الشاشات ({{count}})", "windows": "النوافذ ({{count}})", "emptyTitle": "لم يتم العثور على شاشات أو نوافذ", - "emptyDescription": "إذا كنت قد منحت للتو إذن تسجيل الشاشة، أعد تحميل هذه النافذة. قد تحتاج على macOS إلى إعادة فتح OpenScreen.", - "loadFailedDescription": "تعذّر على OpenScreen تحميل مصادر الالتقاط. أعد تحميل هذه النافذة وحاول مرة أخرى.", + "emptyDescription": "إذا كنت قد منحت للتو إذن تسجيل الشاشة، أعد تحميل هذه النافذة. قد تحتاج على macOS إلى إعادة فتح PurplePrint Studio.", + "loadFailedDescription": "تعذّر على PurplePrint Studio تحميل مصادر الالتقاط. أعد تحميل هذه النافذة وحاول مرة أخرى.", "defaultSourceName": "الشاشة" }, "recording": { @@ -70,7 +70,7 @@ "language": "اللغة", "systemLanguagePrompt": { "title": "هل تريد استخدام لغة نظامك؟", - "description": "اكتشفنا أن {{language}} هي لغة نظامك. هل تريد تبديل OpenScreen إلى {{language}}؟", + "description": "اكتشفنا أن {{language}} هي لغة نظامك. هل تريد تبديل PurplePrint Studio إلى {{language}}؟", "switch": "التبديل إلى {{language}}", "keepDefault": "الاحتفاظ باللغة الحالية" }, @@ -80,7 +80,7 @@ }, "softwareEncoderFallback": { "title": "تم التبديل إلى الترميز البرمجي", - "description": "تعذّر تشغيل مرمّز GPU الافتراضي، لذا تحوّل OpenScreen إلى ترميز H.264 البرمجي. سيستمر التسجيل كالمعتاد، لكن استخدام المعالج قد يكون أعلى.", + "description": "تعذّر تشغيل مرمّز GPU الافتراضي، لذا تحوّل PurplePrint Studio إلى ترميز H.264 البرمجي. سيستمر التسجيل كالمعتاد، لكن استخدام المعالج قد يكون أعلى.", "dismiss": "حسنًا", "dontShowAgain": "عدم الإظهار مرة أخرى" }, diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 6eac95830..fede5273b 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -36,19 +36,19 @@ "zoomOut": "Zoom Out", "toggleFullScreen": "Toggle Full Screen", "recordingStatus": "Recording: {{source}}", - "about": "About OpenScreen", + "about": "About PurplePrint Studio", "services": "Services", - "hide": "Hide OpenScreen", + "hide": "Hide PurplePrint Studio", "hideOthers": "Hide Others", "unhide": "Show All", "saveDiagnostics": "Save Diagnostics" }, "updates": { - "available": "OpenScreen {{latestVersion}} is available. You are using {{currentVersion}}.", - "current": "OpenScreen is up to date ({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}} has been downloaded. Restart to finish installing.", + "available": "PurplePrint Studio {{latestVersion}} is available. You are using {{currentVersion}}.", + "current": "PurplePrint Studio is up to date ({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} has been downloaded. Restart to finish installing.", "blockedRecording": "Finish your recording before installing the update.", - "blockedLocation": "Move OpenScreen to your Applications folder before updating.", + "blockedLocation": "Move PurplePrint Studio to your Applications folder before updating.", "failed": "Could not check for updates.", "downloadFailed": "Could not download the update." }, diff --git a/src/i18n/locales/en/dialogs.json b/src/i18n/locales/en/dialogs.json index 90599af16..e8d455104 100644 --- a/src/i18n/locales/en/dialogs.json +++ b/src/i18n/locales/en/dialogs.json @@ -80,12 +80,12 @@ "saveGif": "Save Exported GIF", "saveVideo": "Save Exported Video", "selectVideo": "Select Video File", - "saveProject": "Save OpenScreen Project", - "openProject": "Open OpenScreen Project", + "saveProject": "Save PurplePrint Studio Project", + "openProject": "Open PurplePrint Studio Project", "gifImage": "GIF Image", "mp4Video": "MP4 Video", "videoFiles": "Video Files", - "openscreenProject": "OpenScreen Project", + "openscreenProject": "PurplePrint Studio Project", "allFiles": "All Files" } } diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index 5948efa47..c5575b7f2 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "The camera could not be opened. Recording without it.", "microphoneDefaulted": "The chosen microphone could not be identified. Recording the default input.", "permissionDenied": "Recording permission denied. Please allow screen recording.", - "accessibilityAllowAndRetry": "Allow Accessibility access for OpenScreen, then press record again to start the countdown.", + "accessibilityAllowAndRetry": "Allow Accessibility access for PurplePrint Studio, then press record again to start the countdown.", "selectSource": "Please select a source to record" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "Your video's transcript will be sent to the provider you choose. Nothing is shared until you connect one." }, "authorUser": "You", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Rewind to this message", "copyMessage": "Copy message", "copiedToClipboard": "Copied to clipboard", @@ -402,12 +402,12 @@ "enterCustomModel": "Enter model ID", "chooseCatalogModel": "Choose from catalog", "pillLocalCli": "Local CLI", - "localCliHint": "Sign in through the official Claude Code CLI. It manages authentication and uses its active subscription, API key, or provider account. OpenScreen does not store or change these credentials.", + "localCliHint": "Sign in through the official Claude Code CLI. It manages authentication and uses its active subscription, API key, or provider account. PurplePrint Studio does not store or change these credentials.", "cliAccountSubscription": "Claude subscription: {{plan}}. Model access and billing follow your plan.", "cliAccountApi": "API authentication is active. API charges apply, not subscription limits.", "cliAccountRuntime": "CLI authentication is active. Usage and billing follow its current configuration.", "checkCliConnection": "Check CLI connection", - "disconnectLocalCli": "Stop using in OpenScreen", + "disconnectLocalCli": "Stop using in PurplePrint Studio", "localCliDeselected": "Claude deselected. Your CLI account remains signed in.", "modelHintCli": "Loaded from the official Claude Code CLI. Model and context access depend on your account; some options may use usage credits.", "title": "AI settings", @@ -442,7 +442,7 @@ "signInChatGPT": "Sign in with ChatGPT", "signInPending": "Finish signing in in your browser. This screen updates automatically.", "cancelSignIn": "Cancel sign-in", - "subscriptionHint": "Requires Codex CLI and an eligible ChatGPT plan. Codex manages sign-in in an OpenScreen-only profile; no API key or token is copied. Your selected plan limits apply." + "subscriptionHint": "Requires Codex CLI and an eligible ChatGPT plan. Codex manages sign-in in an PurplePrint Studio-only profile; no API key or token is copied. Your selected plan limits apply." }, "cpuCompositor": { "notice": "Running without a compatible GPU — rendering on CPU, so playback is slower.", diff --git a/src/i18n/locales/en/launch.json b/src/i18n/locales/en/launch.json index ad2385c02..1341e5a5f 100644 --- a/src/i18n/locales/en/launch.json +++ b/src/i18n/locales/en/launch.json @@ -61,8 +61,8 @@ "screens": "Screens ({{count}})", "windows": "Windows ({{count}})", "emptyTitle": "No screens or windows found", - "emptyDescription": "If you just granted screen recording permission, reload this picker. On macOS you may need to reopen OpenScreen.", - "loadFailedDescription": "OpenScreen could not load capture sources. Reload this picker and try again.", + "emptyDescription": "If you just granted screen recording permission, reload this picker. On macOS you may need to reopen PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio could not load capture sources. Reload this picker and try again.", "defaultSourceName": "Screen" }, "recording": { @@ -74,13 +74,13 @@ "language": "Language", "systemLanguagePrompt": { "title": "Use your system language?", - "description": "We detected {{language}} as your system language. Do you want to switch OpenScreen to {{language}}?", + "description": "We detected {{language}} as your system language. Do you want to switch PurplePrint Studio to {{language}}?", "switch": "Switch to {{language}}", "keepDefault": "Keep current language" }, "softwareEncoderFallback": { "title": "Switched to software encoding", - "description": "The default GPU encoder failed to start, so OpenScreen fell back to software H.264 encoding. Recording should continue as normal, but CPU usage may be higher.", + "description": "The default GPU encoder failed to start, so PurplePrint Studio fell back to software H.264 encoding. Recording should continue as normal, but CPU usage may be higher.", "dismiss": "Got it", "dontShowAgain": "Don't show again" }, diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 29a364265..2d405502e 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -36,19 +36,19 @@ "zoomOut": "Alejar", "toggleFullScreen": "Alternar pantalla completa", "recordingStatus": "Grabando: {{source}}", - "about": "Acerca de OpenScreen", + "about": "Acerca de PurplePrint Studio", "services": "Servicios", - "hide": "Ocultar OpenScreen", + "hide": "Ocultar PurplePrint Studio", "hideOthers": "Ocultar otros", "unhide": "Mostrar todo", "saveDiagnostics": "Guardar diagnósticos" }, "updates": { - "available": "OpenScreen {{latestVersion}} está disponible. Estás usando {{currentVersion}}.", - "current": "OpenScreen está actualizado ({{currentVersion}}).", - "readyToInstall": "Se ha descargado OpenScreen {{latestVersion}}. Reinicia para completar la instalación.", + "available": "PurplePrint Studio {{latestVersion}} está disponible. Estás usando {{currentVersion}}.", + "current": "PurplePrint Studio está actualizado ({{currentVersion}}).", + "readyToInstall": "Se ha descargado PurplePrint Studio {{latestVersion}}. Reinicia para completar la instalación.", "blockedRecording": "Termina la grabación antes de instalar la actualización.", - "blockedLocation": "Mueve OpenScreen a la carpeta Aplicaciones antes de actualizar.", + "blockedLocation": "Mueve PurplePrint Studio a la carpeta Aplicaciones antes de actualizar.", "failed": "No se pudieron buscar actualizaciones.", "downloadFailed": "No se pudo descargar la actualización." }, diff --git a/src/i18n/locales/es/dialogs.json b/src/i18n/locales/es/dialogs.json index 954121938..5a4438203 100644 --- a/src/i18n/locales/es/dialogs.json +++ b/src/i18n/locales/es/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Guardar GIF exportado", "saveVideo": "Guardar video exportado", "selectVideo": "Seleccionar archivo de video", - "saveProject": "Guardar proyecto OpenScreen", - "openProject": "Abrir proyecto OpenScreen", + "saveProject": "Guardar proyecto PurplePrint Studio", + "openProject": "Abrir proyecto PurplePrint Studio", "gifImage": "Imagen GIF", "mp4Video": "Video MP4", "videoFiles": "Archivos de video", - "openscreenProject": "Proyecto OpenScreen", + "openscreenProject": "Proyecto PurplePrint Studio", "allFiles": "Todos los archivos" } } diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index 439ef2415..67f4b62a8 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -37,7 +37,7 @@ "cameraCaptureUnavailable": "No se pudo abrir la cámara. Grabando sin ella.", "microphoneDefaulted": "No se pudo identificar el micrófono elegido. Grabando la entrada predeterminada.", "permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla.", - "accessibilityAllowAndRetry": "Permite el acceso de accesibilidad para OpenScreen y luego pulsa grabar de nuevo para iniciar la cuenta atrás.", + "accessibilityAllowAndRetry": "Permite el acceso de accesibilidad para PurplePrint Studio y luego pulsa grabar de nuevo para iniciar la cuenta atrás.", "selectSource": "Por favor selecciona una fuente para grabar" }, "loadingVideo": "Cargando video...", @@ -273,7 +273,7 @@ "disclaimer": "La transcripción de tu vídeo se enviará al proveedor que elijas. No se comparte nada hasta que conectes uno." }, "authorUser": "Tú", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Rebobinar a este mensaje", "copyMessage": "Copiar mensaje", "copiedToClipboard": "Copiado al portapapeles", @@ -402,12 +402,12 @@ "enterCustomModel": "Introducir ID del modelo", "chooseCatalogModel": "Elegir del catálogo", "pillLocalCli": "CLI local", - "localCliHint": "Inicia sesión en la CLI oficial de Claude Code. La autenticación y el consumo dependen de su suscripción, clave API o cuenta del proveedor activa. OpenScreen no guarda ni cambia estas credenciales.", + "localCliHint": "Inicia sesión en la CLI oficial de Claude Code. La autenticación y el consumo dependen de su suscripción, clave API o cuenta del proveedor activa. PurplePrint Studio no guarda ni cambia estas credenciales.", "cliAccountSubscription": "Suscripción de Claude: {{plan}}. El acceso y la facturación dependen de tu plan.", "cliAccountApi": "La autenticación API está activa. Se aplican cargos de API, no los límites de suscripción.", "cliAccountRuntime": "La CLI está autenticada. El consumo y la facturación siguen su configuración actual.", "checkCliConnection": "Comprobar conexión CLI", - "disconnectLocalCli": "Dejar de usar en OpenScreen", + "disconnectLocalCli": "Dejar de usar en PurplePrint Studio", "localCliDeselected": "Claude deseleccionado. La sesión de la CLI sigue abierta.", "modelHintCli": "Lista obtenida de la CLI oficial de Claude Code. El acceso a modelos y contexto depende de tu cuenta; algunas opciones pueden usar créditos de uso.", "title": "Ajustes de IA", @@ -442,7 +442,7 @@ "signInChatGPT": "Iniciar sesión con ChatGPT", "signInPending": "Completa el inicio de sesión en el navegador. El estado se actualiza automáticamente.", "cancelSignIn": "Cancelar inicio de sesión", - "subscriptionHint": "Requiere Codex CLI y un plan de ChatGPT compatible. Codex gestiona la autenticación en un perfil exclusivo de OpenScreen, sin copiar claves API ni tokens. Se aplican los límites de tu plan." + "subscriptionHint": "Requiere Codex CLI y un plan de ChatGPT compatible. Codex gestiona la autenticación en un perfil exclusivo de PurplePrint Studio, sin copiar claves API ni tokens. Se aplican los límites de tu plan." }, "cpuCompositor": { "notice": "Sin GPU compatible: el renderizado se hace en la CPU, por lo que la reproducción es más lenta.", diff --git a/src/i18n/locales/es/launch.json b/src/i18n/locales/es/launch.json index 7a2c92a55..750636ddf 100644 --- a/src/i18n/locales/es/launch.json +++ b/src/i18n/locales/es/launch.json @@ -61,8 +61,8 @@ "screens": "Pantallas ({{count}})", "windows": "Ventanas ({{count}})", "emptyTitle": "No se encontraron pantallas ni ventanas", - "emptyDescription": "Si acabas de conceder permiso de grabación de pantalla, recarga este selector. En macOS puede que debas volver a abrir OpenScreen.", - "loadFailedDescription": "OpenScreen no pudo cargar las fuentes de captura. Recarga este selector e inténtalo de nuevo.", + "emptyDescription": "Si acabas de conceder permiso de grabación de pantalla, recarga este selector. En macOS puede que debas volver a abrir PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio no pudo cargar las fuentes de captura. Recarga este selector e inténtalo de nuevo.", "defaultSourceName": "Pantalla" }, "recording": { @@ -74,13 +74,13 @@ "language": "Idioma", "systemLanguagePrompt": { "title": "¿Usar el idioma del sistema?", - "description": "Detectamos {{language}} como idioma de tu sistema. ¿Quieres cambiar OpenScreen a {{language}}?", + "description": "Detectamos {{language}} como idioma de tu sistema. ¿Quieres cambiar PurplePrint Studio a {{language}}?", "switch": "Cambiar a {{language}}", "keepDefault": "Mantener idioma actual" }, "softwareEncoderFallback": { "title": "Se cambió a codificación por software", - "description": "El codificador GPU predeterminado no pudo iniciarse, por lo que OpenScreen pasó a la codificación H.264 por software. La grabación debería continuar con normalidad, pero el uso de CPU puede ser mayor.", + "description": "El codificador GPU predeterminado no pudo iniciarse, por lo que PurplePrint Studio pasó a la codificación H.264 por software. La grabación debería continuar con normalidad, pero el uso de CPU puede ser mayor.", "dismiss": "Entendido", "dontShowAgain": "No volver a mostrar" }, diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index c74ef66b8..d46912843 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -36,19 +36,19 @@ "zoomOut": "Zoom arrière", "toggleFullScreen": "Basculer en plein écran", "recordingStatus": "Enregistrement : {{source}}", - "about": "À propos d’OpenScreen", + "about": "À propos d’PurplePrint Studio", "services": "Services", - "hide": "Masquer OpenScreen", + "hide": "Masquer PurplePrint Studio", "hideOthers": "Masquer les autres", "unhide": "Tout afficher", "saveDiagnostics": "Enregistrer les diagnostics" }, "updates": { - "available": "OpenScreen {{latestVersion}} est disponible. Vous utilisez la version {{currentVersion}}.", - "current": "OpenScreen est à jour ({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}} a été téléchargé. Redémarrez pour terminer l'installation.", + "available": "PurplePrint Studio {{latestVersion}} est disponible. Vous utilisez la version {{currentVersion}}.", + "current": "PurplePrint Studio est à jour ({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} a été téléchargé. Redémarrez pour terminer l'installation.", "blockedRecording": "Terminez votre enregistrement avant d'installer la mise à jour.", - "blockedLocation": "Déplacez OpenScreen dans le dossier Applications avant de le mettre à jour.", + "blockedLocation": "Déplacez PurplePrint Studio dans le dossier Applications avant de le mettre à jour.", "failed": "Impossible de rechercher les mises à jour.", "downloadFailed": "Impossible de télécharger la mise à jour." }, diff --git a/src/i18n/locales/fr/dialogs.json b/src/i18n/locales/fr/dialogs.json index 82fc95dcb..a8c99db25 100644 --- a/src/i18n/locales/fr/dialogs.json +++ b/src/i18n/locales/fr/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Enregistrer le GIF exporté", "saveVideo": "Enregistrer la vidéo exportée", "selectVideo": "Sélectionner un fichier vidéo", - "saveProject": "Enregistrer le projet OpenScreen", - "openProject": "Ouvrir un projet OpenScreen", + "saveProject": "Enregistrer le projet PurplePrint Studio", + "openProject": "Ouvrir un projet PurplePrint Studio", "gifImage": "Image GIF", "mp4Video": "Vidéo MP4", "videoFiles": "Fichiers vidéo", - "openscreenProject": "Projet OpenScreen", + "openscreenProject": "Projet PurplePrint Studio", "allFiles": "Tous les fichiers" } } diff --git a/src/i18n/locales/fr/editor.json b/src/i18n/locales/fr/editor.json index 71e71b753..cac3f0f0b 100644 --- a/src/i18n/locales/fr/editor.json +++ b/src/i18n/locales/fr/editor.json @@ -43,7 +43,7 @@ "cameraCaptureUnavailable": "Impossible d'ouvrir la caméra. Enregistrement sans elle.", "microphoneDefaulted": "Micro choisi non identifié. Enregistrement de l'entrée par défaut.", "permissionDenied": "Permission d'enregistrement refusée. Veuillez autoriser l'enregistrement d'écran.", - "accessibilityAllowAndRetry": "Autorisez l'accès Accessibilité pour OpenScreen, puis appuyez de nouveau sur enregistrer pour lancer le compte à rebours.", + "accessibilityAllowAndRetry": "Autorisez l'accès Accessibilité pour PurplePrint Studio, puis appuyez de nouveau sur enregistrer pour lancer le compte à rebours.", "selectSource": "Veuillez sélectionner une source à enregistrer" }, "loadingVideo": "Chargement de la vidéo...", @@ -273,7 +273,7 @@ "disclaimer": "La transcription de votre vidéo sera envoyée au fournisseur que vous choisissez. Rien n'est partagé tant que vous n'en connectez pas un." }, "authorUser": "Vous", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Revenir à ce message", "copyMessage": "Copier le message", "copiedToClipboard": "Copié dans le presse-papiers", @@ -402,12 +402,12 @@ "enterCustomModel": "Saisir un ID de modèle", "chooseCatalogModel": "Choisir dans le catalogue", "pillLocalCli": "CLI locale", - "localCliHint": "Connectez-vous dans la CLI officielle Claude Code. Elle gère l'authentification et utilise son abonnement, sa clé API ou son compte fournisseur actif. OpenScreen ne stocke ni ne modifie ces identifiants.", + "localCliHint": "Connectez-vous dans la CLI officielle Claude Code. Elle gère l'authentification et utilise son abonnement, sa clé API ou son compte fournisseur actif. PurplePrint Studio ne stocke ni ne modifie ces identifiants.", "cliAccountSubscription": "Abonnement Claude : {{plan}}. L'accès aux modèles et la facturation suivent votre formule.", "cliAccountApi": "L'authentification API est active. La tarification API s'applique, pas les limites de l'abonnement.", "cliAccountRuntime": "La CLI est authentifiée. L'utilisation et la facturation suivent sa configuration actuelle.", "checkCliConnection": "Vérifier la connexion CLI", - "disconnectLocalCli": "Ne plus utiliser dans OpenScreen", + "disconnectLocalCli": "Ne plus utiliser dans PurplePrint Studio", "localCliDeselected": "Claude désélectionné. La CLI reste connectée.", "modelHintCli": "Liste obtenue depuis la CLI officielle Claude Code. L'accès aux modèles et au contexte dépend de votre compte ; certaines options peuvent utiliser des crédits d'utilisation.", "title": "Paramètres IA", @@ -442,7 +442,7 @@ "signInChatGPT": "Se connecter avec ChatGPT", "signInPending": "Terminez la connexion dans votre navigateur. Le statut se met à jour automatiquement.", "cancelSignIn": "Annuler la connexion", - "subscriptionHint": "Nécessite Codex CLI et un forfait ChatGPT compatible. Codex gère la connexion dans un profil réservé à OpenScreen, sans copier de clé API ni de jeton. Les limites de votre forfait s’appliquent." + "subscriptionHint": "Nécessite Codex CLI et un forfait ChatGPT compatible. Codex gère la connexion dans un profil réservé à PurplePrint Studio, sans copier de clé API ni de jeton. Les limites de votre forfait s’appliquent." }, "cpuCompositor": { "notice": "Aucun GPU compatible — rendu sur CPU, la lecture est donc plus lente.", diff --git a/src/i18n/locales/fr/launch.json b/src/i18n/locales/fr/launch.json index 858fd58f1..356190603 100644 --- a/src/i18n/locales/fr/launch.json +++ b/src/i18n/locales/fr/launch.json @@ -61,8 +61,8 @@ "screens": "Écrans ({{count}})", "windows": "Fenêtres ({{count}})", "emptyTitle": "Aucun écran ni fenêtre trouvé", - "emptyDescription": "Si vous venez d'accorder l'autorisation d'enregistrement d'écran, rechargez ce sélecteur. Sur macOS, il peut être nécessaire de rouvrir OpenScreen.", - "loadFailedDescription": "OpenScreen n'a pas pu charger les sources de capture. Rechargez ce sélecteur et réessayez.", + "emptyDescription": "Si vous venez d'accorder l'autorisation d'enregistrement d'écran, rechargez ce sélecteur. Sur macOS, il peut être nécessaire de rouvrir PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio n'a pas pu charger les sources de capture. Rechargez ce sélecteur et réessayez.", "defaultSourceName": "Écran" }, "recording": { @@ -74,13 +74,13 @@ "language": "Langue", "systemLanguagePrompt": { "title": "Utiliser la langue de votre système ?", - "description": "Nous avons détecté {{language}} comme langue système. Voulez-vous passer OpenScreen en {{language}} ?", + "description": "Nous avons détecté {{language}} comme langue système. Voulez-vous passer PurplePrint Studio en {{language}} ?", "switch": "Passer en {{language}}", "keepDefault": "Conserver la langue actuelle" }, "softwareEncoderFallback": { "title": "Passage à l'encodage logiciel", - "description": "L'encodeur GPU par défaut n'a pas pu démarrer, OpenScreen est donc passé à l'encodage H.264 logiciel. L'enregistrement devrait continuer normalement, mais l'utilisation du processeur peut être plus élevée.", + "description": "L'encodeur GPU par défaut n'a pas pu démarrer, PurplePrint Studio est donc passé à l'encodage H.264 logiciel. L'enregistrement devrait continuer normalement, mais l'utilisation du processeur peut être plus élevée.", "dismiss": "Compris", "dontShowAgain": "Ne plus afficher" }, diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 24f10fc61..2c60b572a 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -36,19 +36,19 @@ "zoomOut": "Riduci", "toggleFullScreen": "Attiva/disattiva schermo intero", "recordingStatus": "Registrazione: {{source}}", - "about": "Info su OpenScreen", + "about": "Info su PurplePrint Studio", "services": "Servizi", - "hide": "Nascondi OpenScreen", + "hide": "Nascondi PurplePrint Studio", "hideOthers": "Nascondi gli altri", "unhide": "Mostra tutto", "saveDiagnostics": "Salva dati diagnostici" }, "updates": { - "available": "OpenScreen {{latestVersion}} è disponibile. Stai usando la versione {{currentVersion}}.", - "current": "OpenScreen è aggiornato ({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}} è stato scaricato. Riavvia per completare l'installazione.", + "available": "PurplePrint Studio {{latestVersion}} è disponibile. Stai usando la versione {{currentVersion}}.", + "current": "PurplePrint Studio è aggiornato ({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} è stato scaricato. Riavvia per completare l'installazione.", "blockedRecording": "Termina la registrazione prima di installare l'aggiornamento.", - "blockedLocation": "Sposta OpenScreen nella cartella Applicazioni prima di aggiornare.", + "blockedLocation": "Sposta PurplePrint Studio nella cartella Applicazioni prima di aggiornare.", "failed": "Impossibile controllare gli aggiornamenti.", "downloadFailed": "Impossibile scaricare l'aggiornamento." }, diff --git a/src/i18n/locales/it/dialogs.json b/src/i18n/locales/it/dialogs.json index 0fad7d36e..0dacd5563 100644 --- a/src/i18n/locales/it/dialogs.json +++ b/src/i18n/locales/it/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Salva GIF esportata", "saveVideo": "Salva video esportato", "selectVideo": "Seleziona file video", - "saveProject": "Salva progetto OpenScreen", - "openProject": "Apri progetto OpenScreen", + "saveProject": "Salva progetto PurplePrint Studio", + "openProject": "Apri progetto PurplePrint Studio", "gifImage": "Immagine GIF", "mp4Video": "Video MP4", "videoFiles": "File video", - "openscreenProject": "Progetto OpenScreen", + "openscreenProject": "Progetto PurplePrint Studio", "allFiles": "Tutti i file" } } diff --git a/src/i18n/locales/it/editor.json b/src/i18n/locales/it/editor.json index 7fb59738c..f318c34f8 100644 --- a/src/i18n/locales/it/editor.json +++ b/src/i18n/locales/it/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "Impossibile aprire la fotocamera. Registrazione senza di essa.", "microphoneDefaulted": "Impossibile identificare il microfono scelto. Registrazione dall'ingresso predefinito.", "permissionDenied": "Autorizzazione di registrazione negata. Consenti la registrazione dello schermo.", - "accessibilityAllowAndRetry": "Consenti l'accesso all'accessibilità per OpenScreen, poi premi di nuovo registra per avviare il conto alla rovescia.", + "accessibilityAllowAndRetry": "Consenti l'accesso all'accessibilità per PurplePrint Studio, poi premi di nuovo registra per avviare il conto alla rovescia.", "selectSource": "Seleziona una sorgente da registrare" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "La trascrizione del tuo video verrà inviata al fornitore che scegli. Nulla viene condiviso finché non ne colleghi uno." }, "authorUser": "Tu", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Riavvolgi a questo messaggio", "copyMessage": "Copia messaggio", "copiedToClipboard": "Copiato negli appunti", @@ -402,12 +402,12 @@ "enterCustomModel": "Inserisci ID modello", "chooseCatalogModel": "Scegli dal catalogo", "pillLocalCli": "CLI locale", - "localCliHint": "Accedi tramite la CLI ufficiale Claude Code. Gestisce l'autenticazione e usa l'abbonamento, la chiave API o l'account del fornitore attivo. OpenScreen non salva né modifica queste credenziali.", + "localCliHint": "Accedi tramite la CLI ufficiale Claude Code. Gestisce l'autenticazione e usa l'abbonamento, la chiave API o l'account del fornitore attivo. PurplePrint Studio non salva né modifica queste credenziali.", "cliAccountSubscription": "Abbonamento Claude: {{plan}}. Accesso ai modelli e fatturazione dipendono dal piano.", "cliAccountApi": "L'autenticazione API è attiva. Si applicano i costi API, non i limiti dell'abbonamento.", "cliAccountRuntime": "La CLI è autenticata. Utilizzo e fatturazione seguono la configurazione attuale.", "checkCliConnection": "Verifica connessione CLI", - "disconnectLocalCli": "Non usare più in OpenScreen", + "disconnectLocalCli": "Non usare più in PurplePrint Studio", "localCliDeselected": "Claude deselezionato. L'account CLI rimane connesso.", "modelHintCli": "Elenco ottenuto dalla CLI ufficiale Claude Code. L'accesso a modelli e contesto dipende dall'account; alcune opzioni possono usare crediti di utilizzo.", "title": "Impostazioni IA", @@ -442,7 +442,7 @@ "signInChatGPT": "Accedi con ChatGPT", "signInPending": "Completa l’accesso nel browser. Lo stato si aggiorna automaticamente.", "cancelSignIn": "Annulla accesso", - "subscriptionHint": "Richiede Codex CLI e un piano ChatGPT compatibile. Codex gestisce l’accesso in un profilo dedicato a OpenScreen, senza copiare chiavi API o token. Si applicano i limiti del piano." + "subscriptionHint": "Richiede Codex CLI e un piano ChatGPT compatibile. Codex gestisce l’accesso in un profilo dedicato a PurplePrint Studio, senza copiare chiavi API o token. Si applicano i limiti del piano." }, "cpuCompositor": { "notice": "Nessuna GPU compatibile: il rendering avviene sulla CPU, quindi la riproduzione è più lenta.", diff --git a/src/i18n/locales/it/launch.json b/src/i18n/locales/it/launch.json index ed5088d67..84a083f20 100644 --- a/src/i18n/locales/it/launch.json +++ b/src/i18n/locales/it/launch.json @@ -61,8 +61,8 @@ "screens": "Schermi ({{count}})", "windows": "Finestre ({{count}})", "emptyTitle": "Nessuno schermo o finestra trovati", - "emptyDescription": "Se hai appena concesso il permesso di registrazione dello schermo, ricarica questo selettore. Su macOS potrebbe essere necessario riaprire OpenScreen.", - "loadFailedDescription": "OpenScreen non è riuscito a caricare le sorgenti di cattura. Ricarica questo selettore e riprova.", + "emptyDescription": "Se hai appena concesso il permesso di registrazione dello schermo, ricarica questo selettore. Su macOS potrebbe essere necessario riaprire PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio non è riuscito a caricare le sorgenti di cattura. Ricarica questo selettore e riprova.", "defaultSourceName": "Schermo" }, "recording": { @@ -74,13 +74,13 @@ "language": "Lingua", "systemLanguagePrompt": { "title": "Usare la lingua del sistema?", - "description": "Abbiamo rilevato {{language}} come lingua del sistema. Vuoi passare OpenScreen a {{language}}?", + "description": "Abbiamo rilevato {{language}} come lingua del sistema. Vuoi passare PurplePrint Studio a {{language}}?", "switch": "Passa a {{language}}", "keepDefault": "Mantieni la lingua corrente" }, "softwareEncoderFallback": { "title": "Passaggio alla codifica software", - "description": "L'encoder GPU predefinito non si è avviato, quindi OpenScreen è passato alla codifica H.264 software. La registrazione dovrebbe continuare normalmente, ma l'uso della CPU potrebbe essere più alto.", + "description": "L'encoder GPU predefinito non si è avviato, quindi PurplePrint Studio è passato alla codifica H.264 software. La registrazione dovrebbe continuare normalmente, ma l'uso della CPU potrebbe essere più alto.", "dismiss": "Ho capito", "dontShowAgain": "Non mostrare più" }, diff --git a/src/i18n/locales/ja-JP/common.json b/src/i18n/locales/ja-JP/common.json index 0d3ca001b..7db40108d 100644 --- a/src/i18n/locales/ja-JP/common.json +++ b/src/i18n/locales/ja-JP/common.json @@ -36,19 +36,19 @@ "zoomOut": "縮小", "toggleFullScreen": "フルスクリーンを切り替え", "recordingStatus": "録画中: {{source}}", - "about": "OpenScreenについて", + "about": "PurplePrint Studioについて", "services": "サービス", - "hide": "OpenScreenを隠す", + "hide": "PurplePrint Studioを隠す", "hideOthers": "ほかを隠す", "unhide": "すべて表示", "saveDiagnostics": "診断情報を保存" }, "updates": { - "available": "OpenScreen {{latestVersion}} を利用できます。現在のバージョンは {{currentVersion}} です。", - "current": "OpenScreen は最新です({{currentVersion}})。", - "readyToInstall": "OpenScreen {{latestVersion}} をダウンロードしました。再起動してインストールを完了してください。", + "available": "PurplePrint Studio {{latestVersion}} を利用できます。現在のバージョンは {{currentVersion}} です。", + "current": "PurplePrint Studio は最新です({{currentVersion}})。", + "readyToInstall": "PurplePrint Studio {{latestVersion}} をダウンロードしました。再起動してインストールを完了してください。", "blockedRecording": "アップデートをインストールする前に録画を終了してください。", - "blockedLocation": "アップデートする前に OpenScreen をアプリケーションフォルダに移動してください。", + "blockedLocation": "アップデートする前に PurplePrint Studio をアプリケーションフォルダに移動してください。", "failed": "アップデートを確認できませんでした。", "downloadFailed": "アップデートをダウンロードできませんでした。" }, diff --git a/src/i18n/locales/ja-JP/dialogs.json b/src/i18n/locales/ja-JP/dialogs.json index 7ee976a77..ea089083a 100644 --- a/src/i18n/locales/ja-JP/dialogs.json +++ b/src/i18n/locales/ja-JP/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "エクスポートしたGIFを保存", "saveVideo": "エクスポートした動画を保存", "selectVideo": "動画ファイルを選択", - "saveProject": "OpenScreen プロジェクトを保存", - "openProject": "OpenScreen プロジェクトを開く", + "saveProject": "PurplePrint Studio プロジェクトを保存", + "openProject": "PurplePrint Studio プロジェクトを開く", "gifImage": "GIF 画像", "mp4Video": "MP4 動画", "videoFiles": "動画ファイル", - "openscreenProject": "OpenScreen プロジェクト", + "openscreenProject": "PurplePrint Studio プロジェクト", "allFiles": "すべてのファイル" } } diff --git a/src/i18n/locales/ja-JP/editor.json b/src/i18n/locales/ja-JP/editor.json index f653349e0..b2b849a8f 100644 --- a/src/i18n/locales/ja-JP/editor.json +++ b/src/i18n/locales/ja-JP/editor.json @@ -45,7 +45,7 @@ "cameraNotFound": "カメラが見つかりません。", "cameraCaptureUnavailable": "カメラを開けませんでした。カメラなしで録画します。", "microphoneDefaulted": "選択したマイクを特定できませんでした。既定の入力を録音します。", - "accessibilityAllowAndRetry": "OpenScreenにアクセシビリティアクセスを許可してから、もう一度録画を押してカウントダウンを開始してください。", + "accessibilityAllowAndRetry": "PurplePrint Studioにアクセシビリティアクセスを許可してから、もう一度録画を押してカウントダウンを開始してください。", "selectSource": "録画するソースを選択してください" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "動画の文字起こしは、選択したプロバイダーに送信されます。接続するまでは何も共有されません。" }, "authorUser": "あなた", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "このメッセージまで巻き戻す", "copyMessage": "メッセージをコピー", "copiedToClipboard": "クリップボードにコピーしました", @@ -402,12 +402,12 @@ "enterCustomModel": "モデル ID を入力", "chooseCatalogModel": "カタログから選択", "pillLocalCli": "ローカル CLI", - "localCliHint": "公式 Claude Code CLI で直接ログインしてください。認証と利用量は CLI の現在のサブスクリプション・API・プロバイダーアカウントに従います。OpenScreen は認証情報を保存・変更しません。", + "localCliHint": "公式 Claude Code CLI で直接ログインしてください。認証と利用量は CLI の現在のサブスクリプション・API・プロバイダーアカウントに従います。PurplePrint Studio は認証情報を保存・変更しません。", "cliAccountSubscription": "Claude サブスクリプション: {{plan}}。モデルの利用可否と料金はプランに従います。", "cliAccountApi": "API 認証が有効です。サブスクリプションの上限ではなく API 料金が適用されます。", "cliAccountRuntime": "CLI 認証が有効です。利用量と料金は現在の設定に従います。", "checkCliConnection": "CLI 接続を確認", - "disconnectLocalCli": "OpenScreen での選択を解除", + "disconnectLocalCli": "PurplePrint Studio での選択を解除", "localCliDeselected": "Claude の選択を解除しました。CLI のログインは維持されます。", "modelHintCli": "公式 Claude Code CLI から取得した一覧です。モデルとコンテキストの利用可否はアカウントに依存し、一部の選択肢では追加クレジットを使う場合があります。", "title": "AI 設定", @@ -442,7 +442,7 @@ "signInChatGPT": "ChatGPTでログイン", "signInPending": "ブラウザーでログインを完了してください。接続状態は自動更新されます。", "cancelSignIn": "ログインをキャンセル", - "subscriptionHint": "Codex CLIと対応するChatGPTプランが必要です。認証はCodexがOpenScreen専用プロファイルで管理し、APIキーやトークンはコピーしません。プランの利用制限が適用されます。" + "subscriptionHint": "Codex CLIと対応するChatGPTプランが必要です。認証はCodexがPurplePrint Studio専用プロファイルで管理し、APIキーやトークンはコピーしません。プランの利用制限が適用されます。" }, "cpuCompositor": { "notice": "対応 GPU がないため CPU で描画しています。再生が遅くなります。", diff --git a/src/i18n/locales/ja-JP/launch.json b/src/i18n/locales/ja-JP/launch.json index ab9cebbd5..26406b906 100644 --- a/src/i18n/locales/ja-JP/launch.json +++ b/src/i18n/locales/ja-JP/launch.json @@ -61,8 +61,8 @@ "screens": "画面 ({{count}})", "windows": "ウィンドウ ({{count}})", "emptyTitle": "画面またはウィンドウが見つかりません", - "emptyDescription": "画面収録の権限を許可したばかりの場合は、このピッカーを再読み込みしてください。macOSではOpenScreenの再起動が必要な場合があります。", - "loadFailedDescription": "OpenScreenはキャプチャソースを読み込めませんでした。このピッカーを再読み込みしてもう一度お試しください。", + "emptyDescription": "画面収録の権限を許可したばかりの場合は、このピッカーを再読み込みしてください。macOSではPurplePrint Studioの再起動が必要な場合があります。", + "loadFailedDescription": "PurplePrint Studioはキャプチャソースを読み込めませんでした。このピッカーを再読み込みしてもう一度お試しください。", "defaultSourceName": "画面" }, "recording": { @@ -74,13 +74,13 @@ "language": "言語", "systemLanguagePrompt": { "title": "システム言語を使用しますか?", - "description": "システム言語として{{language}}が検出されました。OpenScreenを{{language}}に切り替えますか?", + "description": "システム言語として{{language}}が検出されました。PurplePrint Studioを{{language}}に切り替えますか?", "switch": "{{language}}に切り替え", "keepDefault": "現在の言語を保持" }, "softwareEncoderFallback": { "title": "ソフトウェアエンコードに切り替えました", - "description": "既定のGPUエンコーダーを開始できなかったため、OpenScreenはソフトウェアH.264エンコードに切り替えました。録画はそのまま続行されますが、CPU使用率が高くなる場合があります。", + "description": "既定のGPUエンコーダーを開始できなかったため、PurplePrint StudioはソフトウェアH.264エンコードに切り替えました。録画はそのまま続行されますが、CPU使用率が高くなる場合があります。", "dismiss": "OK", "dontShowAgain": "今後表示しない" }, diff --git a/src/i18n/locales/ko-KR/common.json b/src/i18n/locales/ko-KR/common.json index 98d025d9a..a6ddb1a4c 100644 --- a/src/i18n/locales/ko-KR/common.json +++ b/src/i18n/locales/ko-KR/common.json @@ -36,19 +36,19 @@ "zoomOut": "축소", "toggleFullScreen": "전체 화면 전환", "recordingStatus": "녹화 중: {{source}}", - "about": "OpenScreen 정보", + "about": "PurplePrint Studio 정보", "services": "서비스", - "hide": "OpenScreen 숨기기", + "hide": "PurplePrint Studio 숨기기", "hideOthers": "다른 항목 숨기기", "unhide": "모두 보기", "saveDiagnostics": "진단 정보 저장" }, "updates": { - "available": "OpenScreen {{latestVersion}} 버전을 사용할 수 있습니다. 현재 버전은 {{currentVersion}}입니다.", - "current": "OpenScreen이 최신 버전입니다({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}}을(를) 다운로드했습니다. 다시 시작하여 설치를 완료하세요.", + "available": "PurplePrint Studio {{latestVersion}} 버전을 사용할 수 있습니다. 현재 버전은 {{currentVersion}}입니다.", + "current": "PurplePrint Studio가 최신 버전입니다({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} 버전을 다운로드했습니다. 다시 시작하여 설치를 완료하세요.", "blockedRecording": "업데이트를 설치하기 전에 녹화를 마치세요.", - "blockedLocation": "업데이트하기 전에 OpenScreen을 응용 프로그램 폴더로 옮기세요.", + "blockedLocation": "업데이트하기 전에 PurplePrint Studio를 응용 프로그램 폴더로 옮기세요.", "failed": "업데이트를 확인할 수 없습니다.", "downloadFailed": "업데이트를 다운로드하지 못했습니다." }, diff --git a/src/i18n/locales/ko-KR/dialogs.json b/src/i18n/locales/ko-KR/dialogs.json index 5891f44c1..a78a49f8d 100644 --- a/src/i18n/locales/ko-KR/dialogs.json +++ b/src/i18n/locales/ko-KR/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "내보낸 GIF 저장", "saveVideo": "내보낸 비디오 저장", "selectVideo": "비디오 파일 선택", - "saveProject": "OpenScreen 프로젝트 저장", - "openProject": "OpenScreen 프로젝트 열기", + "saveProject": "PurplePrint Studio 프로젝트 저장", + "openProject": "PurplePrint Studio 프로젝트 열기", "gifImage": "GIF 이미지", "mp4Video": "MP4 비디오", "videoFiles": "비디오 파일", - "openscreenProject": "OpenScreen 프로젝트", + "openscreenProject": "PurplePrint Studio 프로젝트", "allFiles": "모든 파일" } } diff --git a/src/i18n/locales/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json index 6ad83f02a..df1f3e36a 100644 --- a/src/i18n/locales/ko-KR/editor.json +++ b/src/i18n/locales/ko-KR/editor.json @@ -45,7 +45,7 @@ "cameraNotFound": "카메라를 찾을 수 없습니다.", "cameraCaptureUnavailable": "카메라를 열 수 없습니다. 카메라 없이 녹화합니다.", "microphoneDefaulted": "선택한 마이크를 확인할 수 없습니다. 기본 입력을 녹음합니다.", - "accessibilityAllowAndRetry": "OpenScreen의 손쉬운 사용 접근을 허용한 다음, 카운트다운을 시작하려면 다시 녹화를 누르세요.", + "accessibilityAllowAndRetry": "PurplePrint Studio의 손쉬운 사용 접근을 허용한 다음, 카운트다운을 시작하려면 다시 녹화를 누르세요.", "selectSource": "녹화할 소스를 선택해 주세요" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "영상 전사본은 선택한 공급자에게 전송됩니다. 공급자를 연결하기 전까지는 아무것도 공유되지 않습니다." }, "authorUser": "나", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "이 메시지로 되감기", "copyMessage": "메시지 복사", "copiedToClipboard": "클립보드에 복사됨", @@ -402,12 +402,12 @@ "enterCustomModel": "모델 ID 직접 입력", "chooseCatalogModel": "카탈로그에서 선택", "pillLocalCli": "로컬 CLI", - "localCliHint": "공식 Claude Code CLI에 직접 로그인하세요. 인증과 사용량은 CLI의 현재 구독·API·제공업체 계정을 따릅니다. OpenScreen은 이 인증 정보를 저장하거나 변경하지 않습니다.", + "localCliHint": "공식 Claude Code CLI에 직접 로그인하세요. 인증과 사용량은 CLI의 현재 구독·API·제공업체 계정을 따릅니다. PurplePrint Studio는 이 인증 정보를 저장하거나 변경하지 않습니다.", "cliAccountSubscription": "Claude 구독: {{plan}}. 모델 이용 권한과 과금은 해당 플랜을 따릅니다.", "cliAccountApi": "API 인증이 활성화되어 있습니다. 구독 한도가 아닌 API 요금이 적용됩니다.", "cliAccountRuntime": "CLI 인증이 연결되었습니다. 사용량과 과금은 CLI의 현재 설정을 따릅니다.", "checkCliConnection": "CLI 연결 확인", - "disconnectLocalCli": "OpenScreen에서 선택 해제", + "disconnectLocalCli": "PurplePrint Studio에서 선택 해제", "localCliDeselected": "Claude 선택을 해제했습니다. CLI 로그인은 유지됩니다.", "modelHintCli": "공식 Claude Code CLI에서 가져온 목록입니다. 모델·컨텍스트 사용 권한은 계정에 따라 다르며, 일부 옵션은 추가 사용 크레딧을 사용할 수 있습니다.", "title": "AI 설정", @@ -442,7 +442,7 @@ "signInChatGPT": "ChatGPT 계정으로 로그인", "signInPending": "브라우저에서 로그인을 완료해 주세요. 연결 상태가 자동으로 갱신됩니다.", "cancelSignIn": "로그인 취소", - "subscriptionHint": "Codex CLI와 지원되는 ChatGPT 플랜이 필요합니다. 인증은 Codex가 OpenScreen 전용 저장소에서 관리하며 API key나 토큰을 복사하지 않습니다. 구독 플랜의 사용 한도가 적용됩니다." + "subscriptionHint": "Codex CLI와 지원되는 ChatGPT 플랜이 필요합니다. 인증은 Codex가 PurplePrint Studio 전용 저장소에서 관리하며 API key나 토큰을 복사하지 않습니다. 구독 플랜의 사용 한도가 적용됩니다." }, "cpuCompositor": { "notice": "호환되는 GPU가 없어 CPU로 렌더링합니다. 재생이 느려집니다.", diff --git a/src/i18n/locales/ko-KR/launch.json b/src/i18n/locales/ko-KR/launch.json index 192089224..24faeb258 100644 --- a/src/i18n/locales/ko-KR/launch.json +++ b/src/i18n/locales/ko-KR/launch.json @@ -61,8 +61,8 @@ "screens": "화면 ({{count}}개)", "windows": "창 ({{count}}개)", "emptyTitle": "화면이나 창을 찾을 수 없음", - "emptyDescription": "화면 녹화 권한을 방금 허용했다면 이 선택기를 새로고침하세요. macOS에서는 OpenScreen을 다시 열어야 할 수도 있습니다.", - "loadFailedDescription": "OpenScreen이 캡처 소스를 불러오지 못했습니다. 이 선택기를 새로고침한 후 다시 시도하세요.", + "emptyDescription": "화면 녹화 권한을 방금 허용했다면 이 선택기를 새로고침하세요. macOS에서는 PurplePrint Studio를 다시 열어야 할 수도 있습니다.", + "loadFailedDescription": "PurplePrint Studio가 캡처 소스를 불러오지 못했습니다. 이 선택기를 새로고침한 후 다시 시도하세요.", "defaultSourceName": "화면" }, "recording": { @@ -74,13 +74,13 @@ "language": "언어", "systemLanguagePrompt": { "title": "시스템 언어를 사용하시겠습니까?", - "description": "시스템 언어가 {{language}}(으)로 감지되었습니다. OpenScreen을 {{language}}(으)로 전환하시겠습니까?", + "description": "시스템 언어가 {{language}}(으)로 감지되었습니다. PurplePrint Studio를 {{language}}(으)로 전환하시겠습니까?", "switch": "{{language}}(으)로 전환", "keepDefault": "현재 언어 유지" }, "softwareEncoderFallback": { "title": "소프트웨어 인코딩으로 전환됨", - "description": "기본 GPU 인코더를 시작할 수 없어 OpenScreen이 소프트웨어 H.264 인코딩으로 전환했습니다. 녹화는 정상적으로 계속되지만 CPU 사용량이 높아질 수 있습니다.", + "description": "기본 GPU 인코더를 시작할 수 없어 PurplePrint Studio가 소프트웨어 H.264 인코딩으로 전환했습니다. 녹화는 정상적으로 계속되지만 CPU 사용량이 높아질 수 있습니다.", "dismiss": "확인", "dontShowAgain": "다시 표시하지 않음" }, diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index d74e5b215..af81f5303 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -36,19 +36,19 @@ "zoomOut": "Diminuir Zoom", "toggleFullScreen": "Tela Cheia", "recordingStatus": "Gravando: {{source}}", - "about": "Sobre o OpenScreen", + "about": "Sobre o PurplePrint Studio", "services": "Serviços", - "hide": "Ocultar OpenScreen", + "hide": "Ocultar PurplePrint Studio", "hideOthers": "Ocultar Outros", "unhide": "Mostrar Todos", "saveDiagnostics": "Salvar Diagnósticos" }, "updates": { - "available": "O OpenScreen {{latestVersion}} está disponível. Você está usando a versão {{currentVersion}}.", - "current": "O OpenScreen está atualizado ({{currentVersion}}).", - "readyToInstall": "O OpenScreen {{latestVersion}} foi baixado. Reinicie para concluir a instalação.", + "available": "O PurplePrint Studio {{latestVersion}} está disponível. Você está usando a versão {{currentVersion}}.", + "current": "O PurplePrint Studio está atualizado ({{currentVersion}}).", + "readyToInstall": "O PurplePrint Studio {{latestVersion}} foi baixado. Reinicie para concluir a instalação.", "blockedRecording": "Conclua a gravação antes de instalar a atualização.", - "blockedLocation": "Mova o OpenScreen para a pasta Aplicativos antes de atualizar.", + "blockedLocation": "Mova o PurplePrint Studio para a pasta Aplicativos antes de atualizar.", "failed": "Não foi possível verificar atualizações.", "downloadFailed": "Não foi possível baixar a atualização." }, diff --git a/src/i18n/locales/pt-BR/dialogs.json b/src/i18n/locales/pt-BR/dialogs.json index ba77d90a9..9da0ff4da 100644 --- a/src/i18n/locales/pt-BR/dialogs.json +++ b/src/i18n/locales/pt-BR/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Salvar GIF Exportado", "saveVideo": "Salvar Vídeo Exportado", "selectVideo": "Selecionar Arquivo de Vídeo", - "saveProject": "Salvar Projeto OpenScreen", - "openProject": "Abrir Projeto OpenScreen", + "saveProject": "Salvar Projeto PurplePrint Studio", + "openProject": "Abrir Projeto PurplePrint Studio", "gifImage": "Imagem GIF", "mp4Video": "Vídeo MP4", "videoFiles": "Arquivos de Vídeo", - "openscreenProject": "Projeto OpenScreen", + "openscreenProject": "Projeto PurplePrint Studio", "allFiles": "Todos os Arquivos" } } diff --git a/src/i18n/locales/pt-BR/editor.json b/src/i18n/locales/pt-BR/editor.json index 47e6c4bcf..a03c1af6c 100644 --- a/src/i18n/locales/pt-BR/editor.json +++ b/src/i18n/locales/pt-BR/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "Não foi possível abrir a câmera. Gravando sem ela.", "microphoneDefaulted": "Não foi possível identificar o microfone escolhido. Gravando a entrada padrão.", "permissionDenied": "Permissão de gravação negada. Por favor, permita a gravação de tela.", - "accessibilityAllowAndRetry": "Permita o acesso de Acessibilidade para o OpenScreen e pressione gravar novamente para iniciar a contagem regressiva.", + "accessibilityAllowAndRetry": "Permita o acesso de Acessibilidade para o PurplePrint Studio e pressione gravar novamente para iniciar a contagem regressiva.", "selectSource": "Por favor, selecione uma fonte para gravar" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "A transcrição do seu vídeo será enviada ao fornecedor que escolher. Nada é compartilhado até você conectar um." }, "authorUser": "Você", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Rebobinar até esta mensagem", "copyMessage": "Copiar mensagem", "copiedToClipboard": "Copiado para a área de transferência", @@ -402,12 +402,12 @@ "enterCustomModel": "Inserir ID do modelo", "chooseCatalogModel": "Escolher no catálogo", "pillLocalCli": "CLI local", - "localCliHint": "Entre pela CLI oficial do Claude Code. Ela gerencia a autenticação e usa a assinatura, chave de API ou conta de provedor ativa. O OpenScreen não armazena nem altera essas credenciais.", + "localCliHint": "Entre pela CLI oficial do Claude Code. Ela gerencia a autenticação e usa a assinatura, chave de API ou conta de provedor ativa. O PurplePrint Studio não armazena nem altera essas credenciais.", "cliAccountSubscription": "Assinatura Claude: {{plan}}. O acesso aos modelos e a cobrança seguem seu plano.", "cliAccountApi": "A autenticação por API está ativa. Aplicam-se cobranças de API, não os limites da assinatura.", "cliAccountRuntime": "A CLI está autenticada. O uso e a cobrança seguem a configuração atual.", "checkCliConnection": "Verificar conexão CLI", - "disconnectLocalCli": "Parar de usar no OpenScreen", + "disconnectLocalCli": "Parar de usar no PurplePrint Studio", "localCliDeselected": "Claude desmarcado. A conta da CLI continua conectada.", "modelHintCli": "Lista obtida pela CLI oficial do Claude Code. O acesso a modelos e contexto depende da conta; algumas opções podem usar créditos de uso.", "title": "Configurações de IA", @@ -442,7 +442,7 @@ "signInChatGPT": "Entrar com ChatGPT", "signInPending": "Conclua o login no navegador. O status é atualizado automaticamente.", "cancelSignIn": "Cancelar login", - "subscriptionHint": "Requer Codex CLI e um plano ChatGPT compatível. O Codex gerencia o login em um perfil exclusivo do OpenScreen, sem copiar chaves de API ou tokens. Aplicam-se os limites do seu plano." + "subscriptionHint": "Requer Codex CLI e um plano ChatGPT compatível. O Codex gerencia o login em um perfil exclusivo do PurplePrint Studio, sem copiar chaves de API ou tokens. Aplicam-se os limites do seu plano." }, "cpuCompositor": { "notice": "Sem GPU compatível — a renderização ocorre na CPU, então a reprodução fica mais lenta.", diff --git a/src/i18n/locales/pt-BR/launch.json b/src/i18n/locales/pt-BR/launch.json index 4d1fc9270..fe9a3607b 100644 --- a/src/i18n/locales/pt-BR/launch.json +++ b/src/i18n/locales/pt-BR/launch.json @@ -61,8 +61,8 @@ "screens": "Telas ({{count}})", "windows": "Janelas ({{count}})", "emptyTitle": "Nenhuma tela ou janela encontrada", - "emptyDescription": "Se você acabou de conceder a permissão de gravação de tela, recarregue este seletor. No macOS, pode ser necessário reabrir o OpenScreen.", - "loadFailedDescription": "O OpenScreen não conseguiu carregar as fontes de captura. Recarregue este seletor e tente novamente.", + "emptyDescription": "Se você acabou de conceder a permissão de gravação de tela, recarregue este seletor. No macOS, pode ser necessário reabrir o PurplePrint Studio.", + "loadFailedDescription": "O PurplePrint Studio não conseguiu carregar as fontes de captura. Recarregue este seletor e tente novamente.", "defaultSourceName": "Tela" }, "recording": { @@ -74,13 +74,13 @@ "language": "Idioma", "systemLanguagePrompt": { "title": "Usar o idioma do seu sistema?", - "description": "Detectamos {{language}} como o idioma do seu sistema. Deseja mudar o OpenScreen para {{language}}?", + "description": "Detectamos {{language}} como o idioma do seu sistema. Deseja mudar o PurplePrint Studio para {{language}}?", "switch": "Mudar para {{language}}", "keepDefault": "Manter idioma atual" }, "softwareEncoderFallback": { "title": "Mudou para codificação por software", - "description": "O codificador de GPU padrão não pôde ser iniciado, então o OpenScreen passou a usar a codificação H.264 por software. A gravação deve continuar normalmente, mas o uso da CPU pode ser maior.", + "description": "O codificador de GPU padrão não pôde ser iniciado, então o PurplePrint Studio passou a usar a codificação H.264 por software. A gravação deve continuar normalmente, mas o uso da CPU pode ser maior.", "dismiss": "Entendi", "dontShowAgain": "Não mostrar novamente" }, diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 141e8b984..2abab862b 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -36,19 +36,19 @@ "zoomOut": "Уменьшить", "toggleFullScreen": "Полноэкранный режим", "recordingStatus": "Запись: {{source}}", - "about": "О OpenScreen", + "about": "О PurplePrint Studio", "services": "Сервисы", - "hide": "Скрыть OpenScreen", + "hide": "Скрыть PurplePrint Studio", "hideOthers": "Скрыть остальные", "unhide": "Показать все", "saveDiagnostics": "Сохранить диагностику" }, "updates": { - "available": "Доступен OpenScreen {{latestVersion}}. Установлена версия {{currentVersion}}.", - "current": "Установлена последняя версия OpenScreen ({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}} загружен. Перезапустите, чтобы завершить установку.", + "available": "Доступен PurplePrint Studio {{latestVersion}}. Установлена версия {{currentVersion}}.", + "current": "Установлена последняя версия PurplePrint Studio ({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} загружен. Перезапустите, чтобы завершить установку.", "blockedRecording": "Завершите запись перед установкой обновления.", - "blockedLocation": "Переместите OpenScreen в папку «Программы» перед обновлением.", + "blockedLocation": "Переместите PurplePrint Studio в папку «Программы» перед обновлением.", "failed": "Не удалось проверить наличие обновлений.", "downloadFailed": "Не удалось загрузить обновление." }, diff --git a/src/i18n/locales/ru/dialogs.json b/src/i18n/locales/ru/dialogs.json index a821771e9..3cf436924 100644 --- a/src/i18n/locales/ru/dialogs.json +++ b/src/i18n/locales/ru/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Сохранить экспортированный GIF", "saveVideo": "Сохранить экспортированное видео", "selectVideo": "Выбрать видеофайл", - "saveProject": "Сохранить проект OpenScreen", - "openProject": "Открыть проект OpenScreen", + "saveProject": "Сохранить проект PurplePrint Studio", + "openProject": "Открыть проект PurplePrint Studio", "gifImage": "GIF изображение", "mp4Video": "MP4 видео", "videoFiles": "Видеофайлы", - "openscreenProject": "Проект OpenScreen", + "openscreenProject": "Проект PurplePrint Studio", "allFiles": "Все файлы" } } diff --git a/src/i18n/locales/ru/editor.json b/src/i18n/locales/ru/editor.json index 63cb26efb..588093e47 100644 --- a/src/i18n/locales/ru/editor.json +++ b/src/i18n/locales/ru/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "Не удалось открыть камеру. Запись идёт без неё.", "microphoneDefaulted": "Не удалось определить выбранный микрофон. Записывается вход по умолчанию.", "permissionDenied": "Разрешение на запись запрещено. Пожалуйста, разрешите запись экрана.", - "accessibilityAllowAndRetry": "Разрешите OpenScreen доступ к Универсальному доступу, затем снова нажмите запись, чтобы начать обратный отсчет.", + "accessibilityAllowAndRetry": "Разрешите PurplePrint Studio доступ к Универсальному доступу, затем снова нажмите запись, чтобы начать обратный отсчет.", "selectSource": "Пожалуйста, выберите источник для записи" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "Транскрипция вашего видео будет отправлена выбранному провайдеру. Пока вы не подключите провайдера, ничего не отправляется." }, "authorUser": "Вы", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Откатить к этому сообщению", "copyMessage": "Скопировать сообщение", "copiedToClipboard": "Скопировано в буфер обмена", @@ -402,12 +402,12 @@ "enterCustomModel": "Ввести ID модели", "chooseCatalogModel": "Выбрать из каталога", "pillLocalCli": "Локальная CLI", - "localCliHint": "Войдите через официальную CLI Claude Code. Она управляет авторизацией и использует активную подписку, API-ключ или аккаунт провайдера. OpenScreen не хранит и не изменяет эти данные.", + "localCliHint": "Войдите через официальную CLI Claude Code. Она управляет авторизацией и использует активную подписку, API-ключ или аккаунт провайдера. PurplePrint Studio не хранит и не изменяет эти данные.", "cliAccountSubscription": "Подписка Claude: {{plan}}. Доступ к моделям и оплата зависят от вашего плана.", "cliAccountApi": "Активна авторизация по API. Применяется оплата API, а не лимиты подписки.", "cliAccountRuntime": "CLI авторизована. Использование и оплата зависят от текущих настроек.", "checkCliConnection": "Проверить подключение CLI", - "disconnectLocalCli": "Не использовать в OpenScreen", + "disconnectLocalCli": "Не использовать в PurplePrint Studio", "localCliDeselected": "Claude больше не выбран. Вход в CLI сохранён.", "modelHintCli": "Список получен из официальной CLI Claude Code. Доступ к моделям и контексту зависит от аккаунта; некоторые варианты могут расходовать дополнительные кредиты.", "title": "Настройки ИИ", @@ -442,7 +442,7 @@ "signInChatGPT": "Войти через ChatGPT", "signInPending": "Завершите вход в браузере. Статус обновится автоматически.", "cancelSignIn": "Отменить вход", - "subscriptionHint": "Нужны Codex CLI и совместимый тариф ChatGPT. Codex управляет входом в отдельном профиле OpenScreen без копирования API-ключей и токенов. Действуют лимиты вашего тарифа." + "subscriptionHint": "Нужны Codex CLI и совместимый тариф ChatGPT. Codex управляет входом в отдельном профиле PurplePrint Studio без копирования API-ключей и токенов. Действуют лимиты вашего тарифа." }, "cpuCompositor": { "notice": "Совместимый GPU не найден — отрисовка на CPU, поэтому воспроизведение медленнее.", diff --git a/src/i18n/locales/ru/launch.json b/src/i18n/locales/ru/launch.json index 0af9564c4..d66432d69 100644 --- a/src/i18n/locales/ru/launch.json +++ b/src/i18n/locales/ru/launch.json @@ -57,8 +57,8 @@ "screens": "Экраны ({{count}})", "windows": "Окна ({{count}})", "emptyTitle": "Экраны или окна не найдены", - "emptyDescription": "Если вы только что предоставили разрешение на запись экрана, перезагрузите этот выбор источника. На macOS может потребоваться заново открыть OpenScreen.", - "loadFailedDescription": "OpenScreen не удалось загрузить источники захвата. Перезагрузите этот выбор источника и попробуйте снова.", + "emptyDescription": "Если вы только что предоставили разрешение на запись экрана, перезагрузите этот выбор источника. На macOS может потребоваться заново открыть PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio не удалось загрузить источники захвата. Перезагрузите этот выбор источника и попробуйте снова.", "defaultSourceName": "Экран" }, "recording": { @@ -70,7 +70,7 @@ "language": "Язык", "systemLanguagePrompt": { "title": "Использовать системный язык?", - "description": "Мы обнаружили {{language}} как системный язык. Хотите переключить OpenScreen на {{language}}?", + "description": "Мы обнаружили {{language}} как системный язык. Хотите переключить PurplePrint Studio на {{language}}?", "switch": "Переключить на {{language}}", "keepDefault": "Оставить текущий язык" }, @@ -80,7 +80,7 @@ }, "softwareEncoderFallback": { "title": "Выполнен переход на программное кодирование", - "description": "Стандартный GPU-кодировщик не запустился, поэтому OpenScreen перешёл на программное кодирование H.264. Запись продолжится как обычно, но нагрузка на процессор может быть выше.", + "description": "Стандартный GPU-кодировщик не запустился, поэтому PurplePrint Studio перешёл на программное кодирование H.264. Запись продолжится как обычно, но нагрузка на процессор может быть выше.", "dismiss": "Понятно", "dontShowAgain": "Больше не показывать" }, diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 923079b1e..98e29a640 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -36,19 +36,19 @@ "zoomOut": "Uzaklaştır", "toggleFullScreen": "Tam Ekranı Aç/Kapat", "recordingStatus": "Kaydediliyor: {{source}}", - "about": "OpenScreen Hakkında", + "about": "PurplePrint Studio Hakkında", "services": "Servisler", - "hide": "OpenScreen’i Gizle", + "hide": "PurplePrint Studio’i Gizle", "hideOthers": "Diğerlerini Gizle", "unhide": "Tümünü Göster", "saveDiagnostics": "Teşhis Verilerini Kaydet" }, "updates": { - "available": "OpenScreen {{latestVersion}} kullanılabilir. Mevcut sürümünüz {{currentVersion}}.", - "current": "OpenScreen güncel ({{currentVersion}}).", - "readyToInstall": "OpenScreen {{latestVersion}} indirildi. Kurulumu tamamlamak için yeniden başlatın.", + "available": "PurplePrint Studio {{latestVersion}} kullanılabilir. Mevcut sürümünüz {{currentVersion}}.", + "current": "PurplePrint Studio güncel ({{currentVersion}}).", + "readyToInstall": "PurplePrint Studio {{latestVersion}} indirildi. Kurulumu tamamlamak için yeniden başlatın.", "blockedRecording": "Güncellemeyi yüklemeden önce kaydınızı tamamlayın.", - "blockedLocation": "Güncellemeden önce OpenScreen'i Uygulamalar klasörüne taşıyın.", + "blockedLocation": "Güncellemeden önce PurplePrint Studio'i Uygulamalar klasörüne taşıyın.", "failed": "Güncellemeler denetlenemedi.", "downloadFailed": "Güncelleme indirilemedi." }, diff --git a/src/i18n/locales/tr/dialogs.json b/src/i18n/locales/tr/dialogs.json index 196807fd3..66805b6e8 100644 --- a/src/i18n/locales/tr/dialogs.json +++ b/src/i18n/locales/tr/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Dışa Aktarılan GIF'i Kaydet", "saveVideo": "Dışa Aktarılan Videoyu Kaydet", "selectVideo": "Video Dosyası Seç", - "saveProject": "OpenScreen Projesini Kaydet", - "openProject": "OpenScreen Projesini Aç", + "saveProject": "PurplePrint Studio Projesini Kaydet", + "openProject": "PurplePrint Studio Projesini Aç", "gifImage": "GIF Görüntüsü", "mp4Video": "MP4 Video", "videoFiles": "Video Dosyaları", - "openscreenProject": "OpenScreen Projesi", + "openscreenProject": "PurplePrint Studio Projesi", "allFiles": "Tüm Dosyalar" } } diff --git a/src/i18n/locales/tr/editor.json b/src/i18n/locales/tr/editor.json index 2d128298f..c110fcf75 100644 --- a/src/i18n/locales/tr/editor.json +++ b/src/i18n/locales/tr/editor.json @@ -37,7 +37,7 @@ "cameraNotFound": "Kamera bulunamadı.", "cameraCaptureUnavailable": "Kamera açılamadı. Kamerasız kaydediliyor.", "microphoneDefaulted": "Seçilen mikrofon belirlenemedi. Varsayılan giriş kaydediliyor.", - "accessibilityAllowAndRetry": "OpenScreen için Erişilebilirlik erişimine izin verin, ardından geri sayımı başlatmak için tekrar kayda basın.", + "accessibilityAllowAndRetry": "PurplePrint Studio için Erişilebilirlik erişimine izin verin, ardından geri sayımı başlatmak için tekrar kayda basın.", "selectSource": "Lütfen kayıt için bir kaynak seçin" }, "loadingVideo": "Video yükleniyor...", @@ -273,7 +273,7 @@ "disclaimer": "Videonuzun transkripsiyonu seçtiğiniz sağlayıcıya gönderilecek. Bir sağlayıcı bağlamadan hiçbir şey paylaşılmaz." }, "authorUser": "Siz", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Bu mesaja geri sar", "copyMessage": "Mesajı kopyala", "copiedToClipboard": "Panoya kopyalandı", @@ -402,12 +402,12 @@ "enterCustomModel": "Model kimliği gir", "chooseCatalogModel": "Katalogdan seç", "pillLocalCli": "Yerel CLI", - "localCliHint": "Resmî Claude Code CLI üzerinden giriş yapın. Kimlik doğrulama ve kullanım, etkin abonelik, API anahtarı veya sağlayıcı hesabına bağlıdır. OpenScreen bu kimlik bilgilerini saklamaz veya değiştirmez.", + "localCliHint": "Resmî Claude Code CLI üzerinden giriş yapın. Kimlik doğrulama ve kullanım, etkin abonelik, API anahtarı veya sağlayıcı hesabına bağlıdır. PurplePrint Studio bu kimlik bilgilerini saklamaz veya değiştirmez.", "cliAccountSubscription": "Claude aboneliği: {{plan}}. Model erişimi ve faturalandırma planınıza bağlıdır.", "cliAccountApi": "API kimlik doğrulaması etkin. Abonelik sınırları değil, API ücretleri geçerlidir.", "cliAccountRuntime": "CLI kimlik doğrulaması etkin. Kullanım ve faturalandırma mevcut yapılandırmaya bağlıdır.", "checkCliConnection": "CLI bağlantısını denetle", - "disconnectLocalCli": "OpenScreen'de kullanmayı bırak", + "disconnectLocalCli": "PurplePrint Studio'de kullanmayı bırak", "localCliDeselected": "Claude seçimi kaldırıldı. CLI hesabı açık kalır.", "modelHintCli": "Liste resmî Claude Code CLI üzerinden alınır. Model ve bağlam erişimi hesabınıza bağlıdır; bazı seçenekler kullanım kredilerini kullanabilir.", "title": "Yapay zekâ ayarları", @@ -442,7 +442,7 @@ "signInChatGPT": "ChatGPT ile giriş yap", "signInPending": "Tarayıcınızda girişi tamamlayın. Durum otomatik güncellenir.", "cancelSignIn": "Girişi iptal et", - "subscriptionHint": "Codex CLI ve uygun bir ChatGPT planı gerekir. Codex, API anahtarı veya token kopyalamadan OpenScreen’e özel profilde oturum açmayı yönetir. Plan limitleriniz geçerlidir." + "subscriptionHint": "Codex CLI ve uygun bir ChatGPT planı gerekir. Codex, API anahtarı veya token kopyalamadan PurplePrint Studio’e özel profilde oturum açmayı yönetir. Plan limitleriniz geçerlidir." }, "cpuCompositor": { "notice": "Uyumlu GPU yok — işleme CPU üzerinde yapılıyor, bu yüzden oynatma daha yavaş.", diff --git a/src/i18n/locales/tr/launch.json b/src/i18n/locales/tr/launch.json index 632709c82..25d1063e2 100644 --- a/src/i18n/locales/tr/launch.json +++ b/src/i18n/locales/tr/launch.json @@ -61,8 +61,8 @@ "screens": "Ekranlar ({{count}})", "windows": "Pencereler ({{count}})", "emptyTitle": "Ekran veya pencere bulunamadı", - "emptyDescription": "Ekran kaydı iznini az önce verdiyseniz bu seçiciyi yeniden yükleyin. macOS'ta OpenScreen'i yeniden açmanız gerekebilir.", - "loadFailedDescription": "OpenScreen yakalama kaynaklarını yükleyemedi. Bu seçiciyi yeniden yükleyip tekrar deneyin.", + "emptyDescription": "Ekran kaydı iznini az önce verdiyseniz bu seçiciyi yeniden yükleyin. macOS'ta PurplePrint Studio'i yeniden açmanız gerekebilir.", + "loadFailedDescription": "PurplePrint Studio yakalama kaynaklarını yükleyemedi. Bu seçiciyi yeniden yükleyip tekrar deneyin.", "defaultSourceName": "Ekran" }, "recording": { @@ -74,13 +74,13 @@ "language": "Dil", "systemLanguagePrompt": { "title": "Sistem dilinizi kullanmak ister misiniz?", - "description": "Sistem diliniz {{language}} olarak algılandı. OpenScreen i {{language}} diline geçirmek ister misiniz?", + "description": "Sistem diliniz {{language}} olarak algılandı. PurplePrint Studio i {{language}} diline geçirmek ister misiniz?", "switch": "{{language}} diline geç", "keepDefault": "Mevcut dili koru" }, "softwareEncoderFallback": { "title": "Yazılım kodlamasına geçildi", - "description": "Varsayılan GPU kodlayıcı başlatılamadığı için OpenScreen yazılım tabanlı H.264 kodlamasına geçti. Kayıt normal şekilde devam eder, ancak CPU kullanımı daha yüksek olabilir.", + "description": "Varsayılan GPU kodlayıcı başlatılamadığı için PurplePrint Studio yazılım tabanlı H.264 kodlamasına geçti. Kayıt normal şekilde devam eder, ancak CPU kullanımı daha yüksek olabilir.", "dismiss": "Anladım", "dontShowAgain": "Bir daha gösterme" }, diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 089cf992a..f2688188f 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -36,19 +36,19 @@ "zoomOut": "Thu nhỏ", "toggleFullScreen": "Bật/tắt toàn màn hình", "recordingStatus": "Đang ghi: {{source}}", - "about": "Giới thiệu OpenScreen", + "about": "Giới thiệu PurplePrint Studio", "services": "Dịch vụ", - "hide": "Ẩn OpenScreen", + "hide": "Ẩn PurplePrint Studio", "hideOthers": "Ẩn ứng dụng khác", "unhide": "Hiển thị tất cả", "saveDiagnostics": "Lưu thông tin chẩn đoán" }, "updates": { - "available": "Đã có OpenScreen {{latestVersion}}. Bạn đang dùng phiên bản {{currentVersion}}.", - "current": "OpenScreen đang ở phiên bản mới nhất ({{currentVersion}}).", - "readyToInstall": "Đã tải xuống OpenScreen {{latestVersion}}. Khởi động lại để hoàn tất cài đặt.", + "available": "Đã có PurplePrint Studio {{latestVersion}}. Bạn đang dùng phiên bản {{currentVersion}}.", + "current": "PurplePrint Studio đang ở phiên bản mới nhất ({{currentVersion}}).", + "readyToInstall": "Đã tải xuống PurplePrint Studio {{latestVersion}}. Khởi động lại để hoàn tất cài đặt.", "blockedRecording": "Hãy kết thúc bản ghi trước khi cài đặt bản cập nhật.", - "blockedLocation": "Hãy chuyển OpenScreen vào thư mục Applications trước khi cập nhật.", + "blockedLocation": "Hãy chuyển PurplePrint Studio vào thư mục Applications trước khi cập nhật.", "failed": "Không thể kiểm tra bản cập nhật.", "downloadFailed": "Không thể tải xuống bản cập nhật." }, diff --git a/src/i18n/locales/vi/dialogs.json b/src/i18n/locales/vi/dialogs.json index 644d2ba02..132c91524 100644 --- a/src/i18n/locales/vi/dialogs.json +++ b/src/i18n/locales/vi/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "Lưu GIF đã xuất", "saveVideo": "Lưu Video đã xuất", "selectVideo": "Chọn tệp video", - "saveProject": "Lưu dự án OpenScreen", - "openProject": "Mở dự án OpenScreen", + "saveProject": "Lưu dự án PurplePrint Studio", + "openProject": "Mở dự án PurplePrint Studio", "gifImage": "Hình ảnh GIF", "mp4Video": "Video MP4", "videoFiles": "Tệp Video", - "openscreenProject": "Dự án OpenScreen", + "openscreenProject": "Dự án PurplePrint Studio", "allFiles": "Tất cả các tệp" } } diff --git a/src/i18n/locales/vi/editor.json b/src/i18n/locales/vi/editor.json index e039a5b14..0ae139c23 100644 --- a/src/i18n/locales/vi/editor.json +++ b/src/i18n/locales/vi/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "Không thể mở máy ảnh. Đang ghi mà không có máy ảnh.", "microphoneDefaulted": "Không xác định được micrô đã chọn. Đang ghi từ đầu vào mặc định.", "permissionDenied": "Quyền ghi hình bị từ chối. Vui lòng cho phép ghi màn hình.", - "accessibilityAllowAndRetry": "Cho phép OpenScreen truy cập Trợ năng, sau đó nhấn ghi lại để bắt đầu đếm ngược.", + "accessibilityAllowAndRetry": "Cho phép PurplePrint Studio truy cập Trợ năng, sau đó nhấn ghi lại để bắt đầu đếm ngược.", "selectSource": "Vui lòng chọn một nguồn để ghi" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "Bản chép lời video sẽ được gửi đến nhà cung cấp bạn chọn. Không có gì được chia sẻ cho đến khi bạn kết nối một nhà cung cấp." }, "authorUser": "Bạn", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "Tua lại đến tin nhắn này", "copyMessage": "Sao chép tin nhắn", "copiedToClipboard": "Đã sao chép vào bộ nhớ tạm", @@ -402,12 +402,12 @@ "enterCustomModel": "Nhập ID mô hình", "chooseCatalogModel": "Chọn từ danh mục", "pillLocalCli": "CLI cục bộ", - "localCliHint": "Đăng nhập qua CLI Claude Code chính thức. CLI quản lý xác thực và dùng gói đăng ký, khóa API hoặc tài khoản nhà cung cấp hiện tại. OpenScreen không lưu hay thay đổi thông tin xác thực này.", + "localCliHint": "Đăng nhập qua CLI Claude Code chính thức. CLI quản lý xác thực và dùng gói đăng ký, khóa API hoặc tài khoản nhà cung cấp hiện tại. PurplePrint Studio không lưu hay thay đổi thông tin xác thực này.", "cliAccountSubscription": "Gói Claude: {{plan}}. Quyền dùng mô hình và thanh toán theo gói của bạn.", "cliAccountApi": "Đang xác thực bằng API. Áp dụng phí API, không dùng hạn mức đăng ký.", "cliAccountRuntime": "CLI đã xác thực. Mức sử dụng và thanh toán theo cấu hình hiện tại.", "checkCliConnection": "Kiểm tra kết nối CLI", - "disconnectLocalCli": "Ngừng sử dụng trong OpenScreen", + "disconnectLocalCli": "Ngừng sử dụng trong PurplePrint Studio", "localCliDeselected": "Đã bỏ chọn Claude. Tài khoản CLI vẫn đăng nhập.", "modelHintCli": "Danh sách lấy từ CLI Claude Code chính thức. Quyền dùng mô hình và ngữ cảnh phụ thuộc tài khoản; một số tùy chọn có thể dùng tín dụng sử dụng.", "title": "Cài đặt AI", @@ -442,7 +442,7 @@ "signInChatGPT": "Đăng nhập bằng ChatGPT", "signInPending": "Hoàn tất đăng nhập trong trình duyệt. Trạng thái sẽ tự cập nhật.", "cancelSignIn": "Hủy đăng nhập", - "subscriptionHint": "Cần Codex CLI và gói ChatGPT phù hợp. Codex quản lý đăng nhập trong hồ sơ riêng của OpenScreen, không sao chép khóa API hay token. Áp dụng giới hạn gói đăng ký." + "subscriptionHint": "Cần Codex CLI và gói ChatGPT phù hợp. Codex quản lý đăng nhập trong hồ sơ riêng của PurplePrint Studio, không sao chép khóa API hay token. Áp dụng giới hạn gói đăng ký." }, "cpuCompositor": { "notice": "Không có GPU tương thích — kết xuất trên CPU nên phát lại chậm hơn.", diff --git a/src/i18n/locales/vi/launch.json b/src/i18n/locales/vi/launch.json index 98405bf99..f321280b5 100644 --- a/src/i18n/locales/vi/launch.json +++ b/src/i18n/locales/vi/launch.json @@ -57,8 +57,8 @@ "screens": "Màn hình ({{count}})", "windows": "Cửa sổ ({{count}})", "emptyTitle": "Không tìm thấy màn hình hoặc cửa sổ nào", - "emptyDescription": "Nếu bạn vừa cấp quyền ghi màn hình, hãy tải lại bộ chọn này. Trên macOS, bạn có thể cần mở lại OpenScreen.", - "loadFailedDescription": "OpenScreen không thể tải các nguồn ghi hình. Hãy tải lại bộ chọn này và thử lại.", + "emptyDescription": "Nếu bạn vừa cấp quyền ghi màn hình, hãy tải lại bộ chọn này. Trên macOS, bạn có thể cần mở lại PurplePrint Studio.", + "loadFailedDescription": "PurplePrint Studio không thể tải các nguồn ghi hình. Hãy tải lại bộ chọn này và thử lại.", "defaultSourceName": "Màn hình" }, "recording": { @@ -70,7 +70,7 @@ "language": "Ngôn ngữ", "systemLanguagePrompt": { "title": "Sử dụng ngôn ngữ hệ thống của bạn?", - "description": "Chúng tôi phát hiện {{language}} là ngôn ngữ hệ thống của bạn. Bạn có muốn chuyển OpenScreen sang {{language}} không?", + "description": "Chúng tôi phát hiện {{language}} là ngôn ngữ hệ thống của bạn. Bạn có muốn chuyển PurplePrint Studio sang {{language}} không?", "switch": "Chuyển sang {{language}}", "keepDefault": "Giữ ngôn ngữ hiện tại" }, @@ -80,7 +80,7 @@ }, "softwareEncoderFallback": { "title": "Đã chuyển sang mã hóa bằng phần mềm", - "description": "Bộ mã hóa GPU mặc định không khởi động được, nên OpenScreen đã chuyển sang mã hóa H.264 bằng phần mềm. Quá trình ghi vẫn tiếp tục bình thường, nhưng mức sử dụng CPU có thể cao hơn.", + "description": "Bộ mã hóa GPU mặc định không khởi động được, nên PurplePrint Studio đã chuyển sang mã hóa H.264 bằng phần mềm. Quá trình ghi vẫn tiếp tục bình thường, nhưng mức sử dụng CPU có thể cao hơn.", "dismiss": "Đã hiểu", "dontShowAgain": "Không hiển thị lại" }, diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 0a7b2bd9b..9085e25a3 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -36,19 +36,19 @@ "zoomOut": "缩小", "toggleFullScreen": "切换全屏", "recordingStatus": "正在录制:{{source}}", - "about": "关于 OpenScreen", + "about": "关于 PurplePrint Studio", "services": "服务", - "hide": "隐藏 OpenScreen", + "hide": "隐藏 PurplePrint Studio", "hideOthers": "隐藏其他", "unhide": "显示全部", "saveDiagnostics": "保存诊断信息" }, "updates": { - "available": "OpenScreen {{latestVersion}} 已发布。当前版本为 {{currentVersion}}。", - "current": "OpenScreen 已是最新版本({{currentVersion}})。", - "readyToInstall": "OpenScreen {{latestVersion}} 已下载完成。重启以完成安装。", + "available": "PurplePrint Studio {{latestVersion}} 已发布。当前版本为 {{currentVersion}}。", + "current": "PurplePrint Studio 已是最新版本({{currentVersion}})。", + "readyToInstall": "PurplePrint Studio {{latestVersion}} 已下载完成。重启以完成安装。", "blockedRecording": "请先结束录制,再安装更新。", - "blockedLocation": "更新前请将 OpenScreen 移动到「应用程序」文件夹。", + "blockedLocation": "更新前请将 PurplePrint Studio 移动到「应用程序」文件夹。", "failed": "无法检查更新。", "downloadFailed": "无法下载更新。" }, diff --git a/src/i18n/locales/zh-CN/dialogs.json b/src/i18n/locales/zh-CN/dialogs.json index 645ebf9d8..d069bbb6f 100644 --- a/src/i18n/locales/zh-CN/dialogs.json +++ b/src/i18n/locales/zh-CN/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "保存导出的 GIF", "saveVideo": "保存导出的视频", "selectVideo": "选择视频文件", - "saveProject": "保存 OpenScreen 项目", - "openProject": "打开 OpenScreen 项目", + "saveProject": "保存 PurplePrint Studio 项目", + "openProject": "打开 PurplePrint Studio 项目", "gifImage": "GIF 图片", "mp4Video": "MP4 视频", "videoFiles": "视频文件", - "openscreenProject": "OpenScreen 项目", + "openscreenProject": "PurplePrint Studio 项目", "allFiles": "所有文件" } } diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index ba0e52ca9..d6b4e2473 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -45,7 +45,7 @@ "cameraCaptureUnavailable": "无法打开摄像头,正在不使用摄像头录制。", "microphoneDefaulted": "无法识别所选麦克风,正在录制默认输入设备。", "permissionDenied": "录屏权限被拒绝。请允许屏幕录制。", - "accessibilityAllowAndRetry": "允许 OpenScreen 使用辅助功能权限,然后再次按录制以开始倒计时。", + "accessibilityAllowAndRetry": "允许 PurplePrint Studio 使用辅助功能权限,然后再次按录制以开始倒计时。", "selectSource": "请选择要录制的源" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "你视频的转录文本将发送到你选择的提供方。在你连接之前不会共享任何内容。" }, "authorUser": "你", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "回退到此消息", "copyMessage": "复制消息", "copiedToClipboard": "已复制到剪贴板", @@ -402,12 +402,12 @@ "enterCustomModel": "输入模型 ID", "chooseCatalogModel": "从目录中选择", "pillLocalCli": "本地 CLI", - "localCliHint": "请通过官方 Claude Code CLI 登录。认证和用量由 CLI 当前的订阅、API 密钥或服务商账户决定。OpenScreen 不存储或更改这些凭据。", + "localCliHint": "请通过官方 Claude Code CLI 登录。认证和用量由 CLI 当前的订阅、API 密钥或服务商账户决定。PurplePrint Studio 不存储或更改这些凭据。", "cliAccountSubscription": "Claude 订阅:{{plan}}。模型权限和计费遵循该套餐。", "cliAccountApi": "当前使用 API 认证,按 API 计费,不使用订阅额度。", "cliAccountRuntime": "CLI 已认证。用量和计费遵循其当前配置。", "checkCliConnection": "检查 CLI 连接", - "disconnectLocalCli": "在 OpenScreen 中取消选择", + "disconnectLocalCli": "在 PurplePrint Studio 中取消选择", "localCliDeselected": "已取消选择 Claude。CLI 账户仍保持登录。", "modelHintCli": "列表来自官方 Claude Code CLI。模型和上下文权限取决于账户;部分选项可能使用额外用量额度。", "title": "AI 设置", @@ -442,7 +442,7 @@ "signInChatGPT": "使用 ChatGPT 登录", "signInPending": "请在浏览器中完成登录。连接状态会自动更新。", "cancelSignIn": "取消登录", - "subscriptionHint": "需要 Codex CLI 和受支持的 ChatGPT 方案。Codex 在 OpenScreen 专用配置中管理认证,不复制 API 密钥或令牌。适用订阅方案的使用限额。" + "subscriptionHint": "需要 Codex CLI 和受支持的 ChatGPT 方案。Codex 在 PurplePrint Studio 专用配置中管理认证,不复制 API 密钥或令牌。适用订阅方案的使用限额。" }, "cpuCompositor": { "notice": "没有兼容的 GPU,正在使用 CPU 渲染,因此播放较慢。", diff --git a/src/i18n/locales/zh-CN/launch.json b/src/i18n/locales/zh-CN/launch.json index e7efe8eec..f070501f1 100644 --- a/src/i18n/locales/zh-CN/launch.json +++ b/src/i18n/locales/zh-CN/launch.json @@ -61,8 +61,8 @@ "screens": "屏幕 ({{count}})", "windows": "窗口 ({{count}})", "emptyTitle": "未找到屏幕或窗口", - "emptyDescription": "如果您刚刚授予了屏幕录制权限,请重新加载此选择器。在 macOS 上,您可能需要重新打开 OpenScreen。", - "loadFailedDescription": "OpenScreen 无法加载采集源。请重新加载此选择器并重试。", + "emptyDescription": "如果您刚刚授予了屏幕录制权限,请重新加载此选择器。在 macOS 上,您可能需要重新打开 PurplePrint Studio。", + "loadFailedDescription": "PurplePrint Studio 无法加载采集源。请重新加载此选择器并重试。", "defaultSourceName": "屏幕" }, "recording": { @@ -74,13 +74,13 @@ "language": "语言", "systemLanguagePrompt": { "title": "使用系统语言吗?", - "description": "我们检测到你的系统语言是{{language}}。是否将 OpenScreen 切换为{{language}}?", + "description": "我们检测到你的系统语言是{{language}}。是否将 PurplePrint Studio 切换为{{language}}?", "switch": "切换到{{language}}", "keepDefault": "保持当前语言" }, "softwareEncoderFallback": { "title": "已切换到软件编码", - "description": "默认 GPU 编码器启动失败,OpenScreen 已改用软件 H.264 编码。录制会正常继续,但 CPU 占用可能更高。", + "description": "默认 GPU 编码器启动失败,PurplePrint Studio 已改用软件 H.264 编码。录制会正常继续,但 CPU 占用可能更高。", "dismiss": "知道了", "dontShowAgain": "不再提示" }, diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index c54dc17ed..865b0ef85 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -36,19 +36,19 @@ "zoomOut": "縮小", "toggleFullScreen": "切換全螢幕", "recordingStatus": "錄製中:{{source}}", - "about": "關於 OpenScreen", + "about": "關於 PurplePrint Studio", "services": "服務", - "hide": "隱藏 OpenScreen", + "hide": "隱藏 PurplePrint Studio", "hideOthers": "隱藏其他", "unhide": "全部顯示", "saveDiagnostics": "儲存診斷資料" }, "updates": { - "available": "OpenScreen {{latestVersion}} 已推出。目前版本為 {{currentVersion}}。", - "current": "OpenScreen 已是最新版本({{currentVersion}})。", - "readyToInstall": "OpenScreen {{latestVersion}} 已下載完成。請重新啟動以完成安裝。", + "available": "PurplePrint Studio {{latestVersion}} 已推出。目前版本為 {{currentVersion}}。", + "current": "PurplePrint Studio 已是最新版本({{currentVersion}})。", + "readyToInstall": "PurplePrint Studio {{latestVersion}} 已下載完成。請重新啟動以完成安裝。", "blockedRecording": "請先結束錄影,再安裝更新。", - "blockedLocation": "更新前請將 OpenScreen 移至「應用程式」資料夾。", + "blockedLocation": "更新前請將 PurplePrint Studio 移至「應用程式」資料夾。", "failed": "無法檢查更新。", "downloadFailed": "無法下載更新。" }, diff --git a/src/i18n/locales/zh-TW/dialogs.json b/src/i18n/locales/zh-TW/dialogs.json index 14fc364a0..ee3a97409 100644 --- a/src/i18n/locales/zh-TW/dialogs.json +++ b/src/i18n/locales/zh-TW/dialogs.json @@ -79,12 +79,12 @@ "saveGif": "儲存匯出的 GIF", "saveVideo": "儲存匯出的影片", "selectVideo": "選擇影片檔案", - "saveProject": "儲存 OpenScreen 專案", - "openProject": "開啟 OpenScreen 專案", + "saveProject": "儲存 PurplePrint Studio 專案", + "openProject": "開啟 PurplePrint Studio 專案", "gifImage": "GIF 圖片", "mp4Video": "MP4 影片", "videoFiles": "影片檔案", - "openscreenProject": "OpenScreen 專案", + "openscreenProject": "PurplePrint Studio 專案", "allFiles": "所有檔案" } } diff --git a/src/i18n/locales/zh-TW/editor.json b/src/i18n/locales/zh-TW/editor.json index d039b6ba7..2e7ec62e2 100644 --- a/src/i18n/locales/zh-TW/editor.json +++ b/src/i18n/locales/zh-TW/editor.json @@ -45,7 +45,7 @@ "cameraNotFound": "找不到攝影機。", "cameraCaptureUnavailable": "無法開啟攝影機,將在沒有攝影機的情況下錄製。", "microphoneDefaulted": "無法辨識所選麥克風,將錄製預設輸入裝置。", - "accessibilityAllowAndRetry": "允許 OpenScreen 使用輔助使用權限,然後再次按下錄製以開始倒數。", + "accessibilityAllowAndRetry": "允許 PurplePrint Studio 使用輔助使用權限,然後再次按下錄製以開始倒數。", "selectSource": "請選擇要錄製的來源" }, "emptyState": { @@ -273,7 +273,7 @@ "disclaimer": "你影片的轉錄文字會送到你選擇的提供者。在你連接之前不會分享任何內容。" }, "authorUser": "你", - "authorAssistant": "OpenScreen", + "authorAssistant": "PurplePrint Studio", "rewindToMessage": "倒轉至此訊息", "copyMessage": "複製訊息", "copiedToClipboard": "已複製到剪貼簿", @@ -402,12 +402,12 @@ "enterCustomModel": "輸入模型 ID", "chooseCatalogModel": "從目錄選取", "pillLocalCli": "本機 CLI", - "localCliHint": "請透過官方 Claude Code CLI 登入。驗證和用量由 CLI 目前的訂閱、API 金鑰或供應商帳戶決定。OpenScreen 不儲存或變更這些憑證。", + "localCliHint": "請透過官方 Claude Code CLI 登入。驗證和用量由 CLI 目前的訂閱、API 金鑰或供應商帳戶決定。PurplePrint Studio 不儲存或變更這些憑證。", "cliAccountSubscription": "Claude 訂閱:{{plan}}。模型權限和計費遵循該方案。", "cliAccountApi": "目前使用 API 驗證,依 API 計費,不使用訂閱額度。", "cliAccountRuntime": "CLI 已驗證。用量和計費遵循其目前設定。", "checkCliConnection": "檢查 CLI 連線", - "disconnectLocalCli": "在 OpenScreen 中取消選取", + "disconnectLocalCli": "在 PurplePrint Studio 中取消選取", "localCliDeselected": "已取消選取 Claude。CLI 帳戶仍保持登入。", "modelHintCli": "清單來自官方 Claude Code CLI。模型和上下文權限取決於帳戶;部分選項可能使用額外用量額度。", "title": "AI 設定", @@ -442,7 +442,7 @@ "signInChatGPT": "使用 ChatGPT 登入", "signInPending": "請在瀏覽器中完成登入。連線狀態會自動更新。", "cancelSignIn": "取消登入", - "subscriptionHint": "需要 Codex CLI 和支援的 ChatGPT 方案。Codex 在 OpenScreen 專用設定中管理認證,不複製 API 金鑰或權杖。適用訂閱方案的使用額度。" + "subscriptionHint": "需要 Codex CLI 和支援的 ChatGPT 方案。Codex 在 PurplePrint Studio 專用設定中管理認證,不複製 API 金鑰或權杖。適用訂閱方案的使用額度。" }, "cpuCompositor": { "notice": "沒有相容的 GPU,正在使用 CPU 算繪,因此播放較慢。", diff --git a/src/i18n/locales/zh-TW/launch.json b/src/i18n/locales/zh-TW/launch.json index 025e422e7..b90c8bfa4 100644 --- a/src/i18n/locales/zh-TW/launch.json +++ b/src/i18n/locales/zh-TW/launch.json @@ -61,8 +61,8 @@ "screens": "螢幕 ({{count}})", "windows": "視窗 ({{count}})", "emptyTitle": "找不到螢幕或視窗", - "emptyDescription": "如果您剛授予螢幕錄製權限,請重新載入此選擇器。在 macOS 上,您可能需要重新開啟 OpenScreen。", - "loadFailedDescription": "OpenScreen 無法載入擷取來源。請重新載入此選擇器並再試一次。", + "emptyDescription": "如果您剛授予螢幕錄製權限,請重新載入此選擇器。在 macOS 上,您可能需要重新開啟 PurplePrint Studio。", + "loadFailedDescription": "PurplePrint Studio 無法載入擷取來源。請重新載入此選擇器並再試一次。", "defaultSourceName": "螢幕" }, "recording": { @@ -74,13 +74,13 @@ "language": "語言", "systemLanguagePrompt": { "title": "要使用系統語言嗎?", - "description": "偵測到系統語言為 {{language}}。要將 OpenScreen 切換為 {{language}} 嗎?", + "description": "偵測到系統語言為 {{language}}。要將 PurplePrint Studio 切換為 {{language}} 嗎?", "switch": "切換為 {{language}}", "keepDefault": "保持目前語言" }, "softwareEncoderFallback": { "title": "已切換至軟體編碼", - "description": "預設 GPU 編碼器啟動失敗,OpenScreen 已改用軟體 H.264 編碼。錄製會照常繼續,但 CPU 使用率可能較高。", + "description": "預設 GPU 編碼器啟動失敗,PurplePrint Studio 已改用軟體 H.264 編碼。錄製會照常繼續,但 CPU 使用率可能較高。", "dismiss": "知道了", "dontShowAgain": "不再顯示" }, diff --git a/src/lib/captioning/extractMono16kWebDemuxer.test.ts b/src/lib/captioning/extractMono16kWebDemuxer.test.ts new file mode 100644 index 000000000..6d8b9aee0 --- /dev/null +++ b/src/lib/captioning/extractMono16kWebDemuxer.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { webDemuxerWasmUrl } from "./extractMono16kWebDemuxer"; + +describe("webDemuxerWasmUrl", () => { + it("resolves the WASM beside a packaged editor page", () => { + expect( + webDemuxerWasmUrl("file:///C:/Apps/PurplePrint%20Studio/resources/app.asar/dist/index.html"), + ).toBe("file:///C:/Apps/PurplePrint%20Studio/resources/app.asar/dist/wasm/web-demuxer.wasm"); + }); + + it("resolves the same public asset in development", () => { + expect(webDemuxerWasmUrl("http://localhost:5173/index.html")).toBe( + "http://localhost:5173/wasm/web-demuxer.wasm", + ); + }); +}); diff --git a/src/lib/captioning/extractMono16kWebDemuxer.ts b/src/lib/captioning/extractMono16kWebDemuxer.ts index 641f26351..e656dd6aa 100644 --- a/src/lib/captioning/extractMono16kWebDemuxer.ts +++ b/src/lib/captioning/extractMono16kWebDemuxer.ts @@ -6,8 +6,11 @@ const DECODE_QUEUE_BACKPRESSURE = 20; const SOURCE_LOAD_TIMEOUT_MS = 60_000; const READ_END_PADDING_SEC = 0.5; -function webDemuxerWasmUrl(): string { - return new URL("../exporter/wasm/web-demuxer.wasm", window.location.href).href; +export function webDemuxerWasmUrl(pageUrl = window.location.href): string { + // Vite copies public/wasm next to dist/index.html. Resolve from the loaded + // page, not this source file's directory: the latter points outside dist in + // the packaged app and makes silent recordings fail with "Failed to fetch". + return new URL("./wasm/web-demuxer.wasm", pageUrl).href; } /** Mixes one WebCodecs AudioData frame down to mono (averaged across channels). */ diff --git a/src/native/browserShim.ts b/src/native/browserShim.ts index 03904cb1b..67231642e 100644 --- a/src/native/browserShim.ts +++ b/src/native/browserShim.ts @@ -35,7 +35,7 @@ type ShimDesktopSource = { const SHIM_SOURCES: ShimDesktopSource[] = [ { id: "screen:0", name: "Entire Screen", display_id: "0", thumbnail: null, appIcon: null }, { id: "screen:1", name: "Display 2", display_id: "1", thumbnail: null, appIcon: null }, - { id: "window:100", name: "OpenScreen", display_id: "", thumbnail: null, appIcon: null }, + { id: "window:100", name: "PurplePrint Studio", display_id: "", thumbnail: null, appIcon: null }, { id: "window:101", name: "Terminal", display_id: "", thumbnail: null, appIcon: null }, ]; let shimSelectedSource: ShimDesktopSource | null = null; diff --git a/src/styles/design-tokens.css b/src/styles/design-tokens.css index 1f6bf3a56..9312571dc 100644 --- a/src/styles/design-tokens.css +++ b/src/styles/design-tokens.css @@ -1,7 +1,6 @@ -/* OpenScreen design system — light + dark. - * Single source of truth for tokens used by the new editor shell. - * Mint #10b981 is the sole chromatic accent. Red is reserved for - * REC / cut / skip / trim / transcript-highlight only. */ +/* PurplePrint Studio — GraphOS-family light + dark tokens. + * Neutral surfaces carry hierarchy; violet is reserved for brand, focus and + * primary actions. Recording red and semantic status colors keep their meaning. */ /* :root:root (doubled pseudo-class) deliberately outranks a plain `:root` — * src/index.css's shadcn/Tailwind base ALSO declares --accent/--border/ @@ -12,51 +11,51 @@ * depending on import order. */ :root:root { /* canvas */ - --bg: #f8fafc; - --bg-grad: radial-gradient(ellipse 1200px 800px at 50% -200px, #ffffff 0%, #f1f5f9 80%); + --bg: #f7f7f8; + --bg-grad: linear-gradient(180deg, #f7f7f8, #f1f1f2); --surface: #ffffff; - --surface-1: #f1f5f9; - --surface-2: #e2e8f0; - --surface-3: #cbd5e1; + --surface-1: #f1f1f2; + --surface-2: #e9e9ec; + --surface-3: #d4d4d8; --surface-hi: #ffffff; - --surface-warm: #f1f5f9; + --surface-warm: #f1f1f2; /* lines */ - --border: #e2e8f0; - --border-soft: #e6eaef; - --border-hi: #cbd5e1; - --border-dark: #0f172a; + --border: rgba(24, 18, 43, 0.10); + --border-soft: rgba(24, 18, 43, 0.07); + --border-hi: rgba(24, 18, 43, 0.17); + --border-dark: #18181b; /* foreground */ - --fg: #0f172a; - --fg-2: #334155; - --fg-emphasis: #020617; - --muted: #64748b; - --muted-soft: #94a3b8; - --meta: #7c8aa0; - --dim: #b6c2d1; - - /* mint brand */ - --brand: #10b981; - --brand-lo: #059669; - --brand-glow: rgba(16, 185, 129, 0.35); - --brand-soft: rgba(16, 185, 129, 0.10); - --accent: #10b981; - --accent-on: #04221a; - --accent-soft: rgba(16, 185, 129, 0.10); - --accent-wash: rgba(16, 185, 129, 0.06); - --accent-border: rgba(16, 185, 129, 0.26); - --accent-ring: rgba(16, 185, 129, 0.28); - --accent-stripe: rgba(16, 185, 129, 0.30); - - /* timeline playhead — deliberately NOT the mint accent. The scrub cursor + --fg: #3f3f46; + --fg-2: #18181b; + --fg-emphasis: #18181b; + --muted: #71717a; + --muted-soft: #a1a1aa; + --meta: #71717a; + --dim: #a1a1aa; + + /* GraphOS violet: brand, focus and primary actions only */ + --brand: #7c3aed; + --brand-lo: #6d28d9; + --brand-glow: rgba(124, 58, 237, 0.35); + --brand-soft: rgba(124, 58, 237, 0.08); + --accent: #7c3aed; + --accent-on: #ffffff; + --accent-soft: rgba(124, 58, 237, 0.08); + --accent-wash: rgba(124, 58, 237, 0.05); + --accent-border: rgba(124, 58, 237, 0.24); + --accent-ring: rgba(124, 58, 237, 0.35); + --accent-stripe: rgba(124, 58, 237, 0.28); + + /* Timeline playhead deliberately differs from the violet brand. The scrub cursor crosses every lane, including the accent-coloured zoom pills, so it needs a hue of its own to stay readable on top of them. One value for both - themes (the dark block doesn't override it): this violet carries against + themes (the dark block doesn't override it): this blue carries against the light and the dark timeline surfaces alike. */ - --playhead: #6c55ff; - --playhead-glow: rgba(108, 85, 255, 0.68); - --playhead-shadow: rgba(108, 85, 255, 0.35); + --playhead: #60a5fa; + --playhead-glow: rgba(96, 165, 250, 0.68); + --playhead-shadow: rgba(96, 165, 250, 0.35); /* legacy shadcn-style aliases → canonical tokens. Referenced by the TimelinePane / TranscriptEditor / VirtualPreview / NewEditorShell module @@ -86,8 +85,8 @@ --speed-soft: rgba(249, 115, 22, 0.14); --speed-wash: rgba(249, 115, 22, 0.14); - --success: #10b981; - --success-soft: rgba(16, 185, 129, 0.10); + --success: #0f9d76; + --success-soft: rgba(15, 157, 118, 0.10); /* macOS traffic lights */ --light-red: #ff5f57; @@ -115,11 +114,11 @@ --overlay-text-2: rgba(255, 255, 255, 0.55); --overlay-border: rgba(22, 23, 29, 0.06); - /* typography — Geist / Geist Mono are the brand faces, self-hosted by + /* Typography follows GraphOS's Pretendard-first UI; bundled Geist remains a styles/fonts.css. The old system stacks stay on as fallback so a failed font load degrades instead of dropping to a serif default. */ - --font-display: "Geist", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif; - --font-body: "Geist", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif; + --font-display: "Pretendard Variable", "Pretendard", "Geist", system-ui, -apple-system, "Segoe UI", sans-serif; + --font-body: "Pretendard Variable", "Pretendard", "Geist", system-ui, -apple-system, "Segoe UI", sans-serif; --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace; /* size */ @@ -139,10 +138,10 @@ --sp-6: 24px; /* radius */ - --r-xs: 4px; - --r-sm: 6px; - --r-md: 8px; - --r-lg: 12px; + --r-xs: 6px; + --r-sm: 8px; + --r-md: 12px; + --r-lg: 18px; --r-pill: 9999px; /* layout */ @@ -159,7 +158,7 @@ --elev-card-soft: 0 1px 2px rgba(15, 23, 42, 0.03); --elev-pop: 0 8px 24px -8px rgba(15, 23, 42, 0.10), 0 2px 4px rgba(15, 23, 42, 0.04); - --focus-ring: 0 0 0 3px rgba(16, 185, 129, 0.18); + --focus-ring: 0 0 0 3px rgba(124, 58, 237, 0.20); --motion-fast: 120ms; --motion-base: 180ms; @@ -167,38 +166,38 @@ } :root[data-theme="dark"] { - --bg: #0a0d12; - --bg-grad: radial-gradient(ellipse at 50% 0%, #13171f 0%, #0a0d12 60%); - --surface: #14181f; - --surface-1: #14181f; - --surface-2: #1c2029; - --surface-3: #252a35; - --surface-hi: #2b313b; - --surface-warm: #1c2029; - - --fg-emphasis: #ffffff; - --border: #252a35; - --border-soft: #1c2029; - --border-hi: #3a4150; - --border-dark: #0a0d12; - - --fg: #e6e9ef; - --fg-2: #f3f5f8; - --muted: #8b95a3; - --meta: #5b6470; - --dim: #1c2029; - - --brand: #10b981; - --brand-lo: #34d399; - --brand-glow: rgba(16, 185, 129, 0.40); - --brand-soft: rgba(16, 185, 129, 0.14); - --accent: #10b981; - --accent-on: #0a0d12; - --accent-soft: rgba(16, 185, 129, 0.14); - --accent-wash: rgba(16, 185, 129, 0.06); - --accent-border: rgba(16, 185, 129, 0.26); - --accent-ring: rgba(16, 185, 129, 0.32); - --accent-stripe: rgba(16, 185, 129, 0.34); + --bg: #151515; + --bg-grad: linear-gradient(180deg, #151515, #1b1b1b); + --surface: #1b1b1b; + --surface-1: #202020; + --surface-2: #262626; + --surface-3: #2c2c2c; + --surface-hi: #333333; + --surface-warm: #202020; + + --fg-emphasis: #f4f4f5; + --border: rgba(255, 255, 255, 0.07); + --border-soft: rgba(255, 255, 255, 0.05); + --border-hi: rgba(255, 255, 255, 0.14); + --border-dark: #151515; + + --fg: #d4d4d8; + --fg-2: #f4f4f5; + --muted: #a1a1aa; + --meta: #71717a; + --dim: #71717a; + + --brand: #8b5cf6; + --brand-lo: #7c3aed; + --brand-glow: rgba(139, 92, 246, 0.42); + --brand-soft: rgba(139, 92, 246, 0.13); + --accent: #8b5cf6; + --accent-on: #ffffff; + --accent-soft: rgba(139, 92, 246, 0.13); + --accent-wash: rgba(139, 92, 246, 0.06); + --accent-border: rgba(139, 92, 246, 0.28); + --accent-ring: rgba(139, 92, 246, 0.42); + --accent-stripe: rgba(139, 92, 246, 0.32); --danger: #f87171; --danger-lo: #ef4444; @@ -216,8 +215,8 @@ --speed-soft: rgba(251, 146, 60, 0.16); --speed-wash: rgba(251, 146, 60, 0.16); - --success: #10b981; - --success-soft: rgba(16, 185, 129, 0.14); + --success: #43d2a2; + --success-soft: rgba(67, 210, 162, 0.14); --scene-sky-hi: #2e3440; --scene-sky-mid: #252a35; @@ -242,5 +241,5 @@ --elev-card: 0 1px 2px rgba(0, 0, 0, 0.40), 0 4px 12px -4px rgba(0, 0, 0, 0.50); --elev-card-soft: 0 1px 2px rgba(0, 0, 0, 0.30); --elev-pop: 0 8px 24px -8px rgba(0, 0, 0, 0.60); - --focus-ring: 0 0 0 3px rgba(16, 185, 129, 0.30); + --focus-ring: 0 0 0 3px rgba(139, 92, 246, 0.30); }