diff --git a/.github/rulesets/README.md b/.github/rulesets/README.md index 0ed0b49..0a63579 100644 --- a/.github/rulesets/README.md +++ b/.github/rulesets/README.md @@ -5,16 +5,18 @@ These JSON files are import-ready repository rulesets for `sky-systems/sky_phone ## Activation order 1. Merge the governance files and workflows into the default `dev` branch. -2. Let `CI` and `Pull request policy` run once so GitHub registers the check names. +2. Let `CI`, `Automated code review`, and `Pull request policy` run once so GitHub registers the check names. 3. Open **Settings > Rules > Rulesets > New ruleset > Import a ruleset**. 4. Import `protect-dev.json`, review its target and required checks, then activate it. 5. Import `protect-release-tags.json`, review the numeric tag pattern, then activate it. -6. Open a test pull request and confirm all three required checks are reported before relying on the ruleset. +6. Open a test pull request and confirm all five required checks and the test-resource comment are reported before relying on the ruleset. -`protect-dev.json` blocks deletion and force pushes, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, and `Pull request policy` checks. +`protect-dev.json` blocks deletion and force pushes, restricts all updates of `dev` to the built-in `Maintain` role, requires one approval, dismisses stale reviews, requires approval after the last push, resolves review conversations, and requires the `Repository policy`, `Frontend`, `CodeQL`, `Dependency review`, and `Pull request policy` checks. Maintainers can bypass rules only through a pull request, so they cannot use this bypass for a direct push to `dev`. + +Successful pull requests receive a 14-day test-resource artifact containing the deployable `sky_phone` folder and built NUI. `Pull request test resource link` runs after `CI` and maintains one download comment in the pull request. It does not check out, download, or execute pull-request content with its write-capable token. `protect-release-tags.json` accepts stable numeric semantic versions such as `0.2.0`, rejects a leading `v`, and makes created tags immutable. -The built-in repository `Maintain` role (`RepositoryRole` actor ID `2`) has an explicit emergency bypass in both files. Use it only for audited recovery work. Remove the bypass in GitHub if maintainers should remain subject to every rule; changing the committed JSON alone does not update an already imported ruleset. +The built-in repository `Maintain` role uses `RepositoryRole` actor ID `2`. Its branch bypass is limited to pull requests and is what permits maintainers to merge; its tag bypass is always available so maintainers can create and recover releases. Changing the committed JSON alone does not update an already imported ruleset. Rulesets are GitHub settings, not live configuration files. Committing or editing these JSON files does not activate or update protection automatically; an administrator must import or reconcile them in GitHub. diff --git a/.github/rulesets/protect-dev.json b/.github/rulesets/protect-dev.json index c07ead8..5b1bdc9 100644 --- a/.github/rulesets/protect-dev.json +++ b/.github/rulesets/protect-dev.json @@ -16,6 +16,12 @@ { "type": "non_fast_forward" }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": false + } + }, { "type": "pull_request", "parameters": { @@ -38,6 +44,12 @@ { "context": "Frontend" }, + { + "context": "CodeQL" + }, + { + "context": "Dependency review" + }, { "context": "Pull request policy" } @@ -50,7 +62,7 @@ { "actor_id": 2, "actor_type": "RepositoryRole", - "bypass_mode": "always" + "bypass_mode": "pull_request" } ] } diff --git a/.github/scripts/validate-repository.mjs b/.github/scripts/validate-repository.mjs index 3706f53..11feff4 100644 --- a/.github/scripts/validate-repository.mjs +++ b/.github/scripts/validate-repository.mjs @@ -31,6 +31,8 @@ const rulesetFiles = (await readdir(rulesetDirectory)).filter((file) => const requiredContexts = new Set([ "Repository policy", "Frontend", + "CodeQL", + "Dependency review", "Pull request policy", ]); @@ -53,14 +55,23 @@ for (const file of rulesetFiles) { fail(`${file} must contain at least one rule`); } + const expectedBypassMode = + ruleset.target === "branch" ? "pull_request" : "always"; const maintainBypass = ruleset.bypass_actors?.some( (actor) => actor.actor_type === "RepositoryRole" && actor.actor_id === 2 && - actor.bypass_mode === "always", + actor.bypass_mode === expectedBypassMode, ); if (!maintainBypass) { - fail(`${file} must retain the Maintain role emergency bypass`); + fail(`${file} must retain the expected Maintain role bypass`); + } + + if ( + ruleset.target === "branch" && + !ruleset.rules?.some((rule) => rule.type === "update") + ) { + fail(`${file} must restrict default-branch updates to bypass actors`); } const statusRule = ruleset.rules?.find( diff --git a/.github/workflows/automated-code-review.yml b/.github/workflows/automated-code-review.yml new file mode 100644 index 0000000..3ebad9d --- /dev/null +++ b/.github/workflows/automated-code-review.yml @@ -0,0 +1,52 @@ +name: Automated code review + +on: + pull_request: + push: + branches: + - dev + workflow_dispatch: + +concurrency: + group: automated-review-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + codeql: + name: CodeQL + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + packages: read + security-events: write + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: javascript-typescript + + - name: Analyze JavaScript and TypeScript + uses: github/codeql-action/analyze@v4 + with: + category: /language:javascript-typescript + + dependency-review: + name: Dependency review + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Review dependency changes + uses: actions/dependency-review-action@v5 + with: + fail-on-severity: high diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de6b06b..d02c0f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,3 +92,28 @@ jobs: - name: Verify published NUI entrypoint run: test -f ../sky_phone/source/html/index.html + + - name: Package pull request test resource + if: github.event_name == 'pull_request' + shell: bash + working-directory: . + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + artifact_name="sky_phone-pr-${PR_NUMBER}-${HEAD_SHA}" + mkdir -p artifacts + zip -r "artifacts/${artifact_name}.zip" sky_phone + unzip -t "artifacts/${artifact_name}.zip" + unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/fxmanifest.lua' + unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/source/html/index.html' + + - name: Upload pull request test resource + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v7 + with: + path: artifacts/sky_phone-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}.zip + archive: false + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/pr-test-resource-comment.yml b/.github/workflows/pr-test-resource-comment.yml new file mode 100644 index 0000000..7249d75 --- /dev/null +++ b/.github/workflows/pr-test-resource-comment.yml @@ -0,0 +1,103 @@ +name: Pull request test resource link + +on: + workflow_run: + workflows: + - CI + types: + - completed + +permissions: + actions: read + contents: read + pull-requests: write + +jobs: + comment: + name: Link test resource + if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Create or update pull request comment + uses: actions/github-script@v9 + with: + script: | + const marker = ''; + const run = context.payload.workflow_run; + const pullRequest = run.pull_requests?.[0]; + + if (!pullRequest) { + core.notice('The CI run is not associated with a pull request.'); + return; + } + + const { owner, repo } = context.repo; + const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`; + let body; + + if (run.conclusion === 'success') { + const response = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: run.id, + per_page: 100, + }); + const prefix = `sky_phone-pr-${pullRequest.number}-`; + const artifact = response.data.artifacts.find( + (candidate) => + !candidate.expired && + candidate.name.startsWith(prefix) && + /^sky_phone-pr-\d+-[0-9a-f]{40}\.zip$/.test(candidate.name), + ); + + if (!artifact) { + core.setFailed('CI succeeded without a pull request test resource artifact.'); + return; + } + + const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}/artifacts/${artifact.id}`; + body = [ + marker, + '## Sky Phone Test-Resource', + '', + `✅ Der aktuelle Pull Request wurde erfolgreich geprüft und gebaut. [Test-Resource herunterladen](${artifactUrl}).`, + '', + 'Das ZIP enthält den deploybaren `sky_phone`-Ordner inklusive gebautem Frontend und bleibt 14 Tage verfügbar.', + '', + `[Workflow-Lauf öffnen](${runUrl})`, + ].join('\n'); + } else { + body = [ + marker, + '## Sky Phone Test-Resource', + '', + `❌ Für den aktuellen Stand wurde keine freigegebene Test-Resource erzeugt, weil der [CI-Lauf](${runUrl}) nicht erfolgreich war.`, + ].join('\n'); + } + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: pullRequest.number, + per_page: 100, + }); + const existing = comments.find( + (comment) => comment.user?.type === 'Bot' && comment.body?.includes(marker), + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pullRequest.number, + body, + }); + } diff --git a/AGENTS.md b/AGENTS.md index 1138622..7f5f0d7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,44 @@ This repository contains the standalone FiveM resource `sky_phone`. These rules apply throughout the repository unless a more specific `AGENTS.md` adds stricter requirements. +## Repository governance for AI contributors + +AI agents and automated coding tools must treat the repository governance files +as project instructions, not as optional documentation. + +- Before changing code, read `CONTRIBUTING.md` and every more specific + `AGENTS.md` that applies to the target files. For issue or pull-request work, + also read the matching file under `.github/ISSUE_TEMPLATE` and + `.github/PULL_REQUEST_TEMPLATE.md`. +- Before changing CI, packaging, releases, contribution policy, branch policy, + or dependency handling, inspect all relevant files under `.github/workflows`, + `.github/rulesets`, and `.github/scripts`. Keep their contracts synchronized. +- Preserve the required check names `Repository policy`, `Frontend`, `CodeQL`, + `Dependency review`, and `Pull request policy`. If a task intentionally + renames or replaces one, update `.github/rulesets/protect-dev.json`, + `.github/scripts/validate-repository.mjs`, `.github/rulesets/README.md`, and + the corresponding workflow in the same change. +- Anyone may open a pull request, but only collaborators with GitHub's built-in + `Maintain` role may merge into `dev`, and maintainers must merge through a + pull request. Preserve this behavior in branch rules and documentation. +- Treat `.github/rulesets/*.json` as the version-controlled ruleset baseline. + Editing or merging these files does not update GitHub settings automatically; + report that an administrator must import or reconcile the live ruleset. +- Preserve the secure PR artifact boundary: untrusted pull-request code may + build the test resource only with read permissions. A workflow with write + permissions may inspect trusted GitHub metadata and maintain the PR comment, + but must never check out, download, extract, import, or execute PR-controlled + code or artifacts. +- PR and release packages must contain one top-level `sky_phone` directory with + `fxmanifest.lua` and the built NUI at `source/html/index.html`. Build generated + NUI from `frontend`; never hand-edit `sky_phone/source/html`. +- Keep automated review and test-resource findings visible. Do not weaken, + skip, or silence validation merely to make a check pass. Automated review + complements and never replaces the required human maintainer review. +- After governance changes, run the repository validator, Prettier, and + actionlint where available. Inspect the final diff and stage only files that + belong to the task. + ## Architecture and security - Keep `sky_phone` independent. Do not add dependencies or integrations with diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66683c4..dd61cce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,10 +75,12 @@ pnpm build The build publishes the generated NUI into `sky_phone/source/html`. Do not hand-edit generated output. A successful build proves source/build consistency, not behavior inside FiveM; report live runtime testing separately. +Every pull request also receives an automated CodeQL scan and dependency review. After the full CI run succeeds, GitHub packages the deployable `sky_phone` folder as a test-resource ZIP and adds or updates a download link in the pull request. The artifact is retained for 14 days. It is suitable for manual testing on a test server, but it is not a release and does not replace live FiveM validation. + Lua, config, manifest, locale, SQL, and native changes must also be tested in a restarted FiveM resource with experimental OAL enabled. Pass native coordinates as separate numeric arguments and verify native signatures against authoritative documentation. ## Review and merge -A pull request is ready when required checks pass, review conversations are resolved, the latest push is approved by someone other than its author, and migrations or operational steps are explicit. The default ruleset allows merge, squash, and rebase so maintainers can preserve meaningful merge history when needed. +A pull request is ready when the repository policy, frontend, CodeQL, dependency review, and pull-request policy checks pass; review conversations are resolved; the latest push is approved by someone other than its author; and migrations or operational steps are explicit. Automated findings complement rather than replace the human maintainer review. Anyone may open a pull request, but only collaborators with the built-in GitHub `Maintain` role may merge into `dev`. Maintainers must merge through a pull request; the ruleset does not permit direct pushes to `dev`. The default ruleset allows merge, squash, and rebase so maintainers can preserve meaningful merge history when needed. Release tags use numeric semantic versions without a `v` prefix, for example `0.2.0`. Tags are immutable after creation. diff --git a/README.md b/README.md index 7064f06..cd74942 100644 --- a/README.md +++ b/README.md @@ -114,25 +114,27 @@ Sky Phone is built to be the **free FiveM phone you can choose without accepting ### Required -- MySQL or MariaDB -- `oxmysql` -- One supported framework: - - ESX Legacy (`es_extended`) - - Qbox (`qbx_core`) - - QBCore (`qb-core`) -- One supported inventory path: - - `ox_inventory` - - `qb-inventory` - - `lj-inventory` - - `qs-inventory` - - `codem-inventory` - - `core_inventory` - - `mf-inventory` - - `smx-inventory` - - `hex_4_inventory` - - Native ESX inventory +| Requirement | Supported options | +| --- | --- | +| **Database** | MySQL or MariaDB | +| **Database bridge** | `oxmysql` | +| **Framework** | ESX Legacy (`es_extended`), QBCore (`qb-core`), or Qbox (`qbx_core`) | +| **Inventory** | Choose one supported adapter from the table below | -`mf-inventory` and `smx-inventory` are supported with ESX. The native ESX and HEX adapters use count-based items and therefore require unique phones and physical SIM cards to be disabled. +| Inventory | Metadata support | Unique Phones | Notes | +| --- | --- | --- | --- | +| `ox_inventory` | Yes | Yes | Full per-item phone and physical SIM metadata | +| `qb-inventory` | Yes | Yes | Uses item `info` metadata | +| `lj-inventory` | Yes | Yes | QBCore inventory with item `info` metadata | +| `qs-inventory` | Yes | Yes | Full per-slot metadata | +| `codem-inventory` | Yes | Yes | Full per-slot metadata | +| `core_inventory` | Yes | Yes | Full per-slot metadata | +| `mf-inventory` | Yes | Yes | Supported with ESX | +| `smx-inventory` | Yes | Yes | Supported with ESX through the player metadata bridge | +| `hex_4_inventory` | **No metadata support** | **No, Unique Phones are not possible** | ESX only; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` | +| Native ESX inventory | **No metadata support** | **No, Unique Phones are not possible** | Count-based items; set `Config.Phone.Unique = false` and `Config.Sim.Enabled = false` | + +`hex_4_inventory` and native ESX inventory cannot persist per-item metadata. Unique Phones and physical SIM cards are therefore unavailable with these adapters. ### Voice diff --git a/frontend/package.json b/frontend/package.json index b562569..2e410a3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,11 @@ "test": "vitest run && pnpm test:browser-mocks", "test:browser-mocks": "node testserver/smoke.cjs" }, + "pnpm": { + "overrides": { + "nanoid@<3.3.18": "3.3.18" + } + }, "dependencies": { "@tiptap/core": "^3.29.2", "@tiptap/extension-placeholder": "^3.29.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 2d07394..5c01961 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + nanoid@<3.3.18: 3.3.18 + importers: .: @@ -1940,8 +1943,8 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.17: - resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -4351,7 +4354,7 @@ snapshots: muggle-string@0.4.1: {} - nanoid@3.3.17: {} + nanoid@3.3.18: {} natural-compare@1.4.0: {} @@ -4449,7 +4452,7 @@ snapshots: postcss@8.5.25: dependencies: - nanoid: 3.3.17 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 diff --git a/frontend/public/img/wallpapers/sky-phone-demo-gradient.png b/frontend/public/img/wallpapers/sky-phone-demo-gradient.png new file mode 100644 index 0000000..2c7f53a Binary files /dev/null and b/frontend/public/img/wallpapers/sky-phone-demo-gradient.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index dc8cd20..5289d74 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -268,8 +268,13 @@ const PHONE_BASE_SCALE = 0.69 * 1.2 const PHONE_PORTRAIT_WIDTH = 390 const PHONE_PORTRAIT_HEIGHT = 844 const MIN_PRODUCTION_PHONE_ZOOM = 260 / PHONE_PORTRAIT_WIDTH -const isDevelopment = import.meta.env.DEV const developmentParameters = new URLSearchParams(window.location.search) +const isBrowserPreview = + developmentParameters.has('browserPreview') && + developmentParameters.get('apiBase')?.startsWith('/') === true +const isDevelopment = + import.meta.env.DEV || + developmentParameters.get('apiBase')?.startsWith('/') === true const developmentLockScreenPreview = isDevelopment && developmentParameters.has('lockScreenPreview') @@ -433,6 +438,14 @@ let simPickerClosePending = false function getViewportScale(): number { const heightScale = window.innerHeight / REFERENCE_VIEWPORT_HEIGHT + if (isBrowserPreview) { + const availableScale = Math.min( + window.innerWidth / PHONE_PORTRAIT_WIDTH, + window.innerHeight / PHONE_PORTRAIT_HEIGHT, + ) + + return (availableScale * 0.94) / PHONE_BASE_SCALE + } if (isDevelopment) return heightScale return Math.min(window.innerWidth / REFERENCE_VIEWPORT_WIDTH, heightScale) @@ -1583,6 +1596,7 @@ onBeforeUnmount(() => { " class="phone-stage" :class="{ + 'phone-stage--browser-preview': isBrowserPreview, 'phone-stage--landscape': phone.cameraLandscape, 'phone-stage--peek': notifications.isPeeking, }" diff --git a/frontend/src/AppDevelopmentPreview.contract.test.ts b/frontend/src/AppDevelopmentPreview.contract.test.ts index af0d7eb..3246879 100644 --- a/frontend/src/AppDevelopmentPreview.contract.test.ts +++ b/frontend/src/AppDevelopmentPreview.contract.test.ts @@ -84,4 +84,16 @@ describe('browser development preview contract', () => { ) expect(mainCss).not.toContain('.phone-stage--dev') }) + + it('fills the dedicated browser embed without clipping the hardware controls', () => { + expect(source).toContain("developmentParameters.has('browserPreview')") + expect(source).toContain("'phone-stage--browser-preview': isBrowserPreview") + expect(source).toContain('return (availableScale * 0.94) / PHONE_BASE_SCALE') + expect(mainCss).toMatch( + /\.phone-stage--browser-preview\s*\{[^}]*place-items:\s*center;[^}]*padding:\s*0;/s, + ) + expect(mainCss).toMatch( + /\.phone-stage--browser-preview \.phone-device\s*\{[^}]*filter:\s*none;/s, + ) + }) }) diff --git a/frontend/src/assets/img/app-icons/billing.svg b/frontend/src/assets/img/app-icons/billing.svg deleted file mode 100644 index 5cc30ba..0000000 --- a/frontend/src/assets/img/app-icons/billing.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/billing.webp b/frontend/src/assets/img/app-icons/billing.webp new file mode 100644 index 0000000..9ee5e84 Binary files /dev/null and b/frontend/src/assets/img/app-icons/billing.webp differ diff --git a/frontend/src/assets/img/app-icons/calendar.svg b/frontend/src/assets/img/app-icons/calendar.svg deleted file mode 100644 index 6c34aa8..0000000 --- a/frontend/src/assets/img/app-icons/calendar.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/calendar.webp b/frontend/src/assets/img/app-icons/calendar.webp new file mode 100644 index 0000000..af607ad Binary files /dev/null and b/frontend/src/assets/img/app-icons/calendar.webp differ diff --git a/frontend/src/assets/img/app-icons/citywarn.webp b/frontend/src/assets/img/app-icons/citywarn.webp index d15f7c6..083957e 100644 Binary files a/frontend/src/assets/img/app-icons/citywarn.webp and b/frontend/src/assets/img/app-icons/citywarn.webp differ diff --git a/frontend/src/assets/img/app-icons/companies.svg b/frontend/src/assets/img/app-icons/companies.svg deleted file mode 100644 index b52f7ce..0000000 --- a/frontend/src/assets/img/app-icons/companies.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/companies.webp b/frontend/src/assets/img/app-icons/companies.webp new file mode 100644 index 0000000..5f5ee8e Binary files /dev/null and b/frontend/src/assets/img/app-icons/companies.webp differ diff --git a/frontend/src/assets/img/app-icons/crewlink.svg b/frontend/src/assets/img/app-icons/crewlink.svg deleted file mode 100644 index 27e2f21..0000000 --- a/frontend/src/assets/img/app-icons/crewlink.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/crewlink.webp b/frontend/src/assets/img/app-icons/crewlink.webp new file mode 100644 index 0000000..7e35b75 Binary files /dev/null and b/frontend/src/assets/img/app-icons/crewlink.webp differ diff --git a/frontend/src/assets/img/app-icons/crypto-header-logo.png b/frontend/src/assets/img/app-icons/crypto-header-logo.png deleted file mode 100644 index 734bf52..0000000 Binary files a/frontend/src/assets/img/app-icons/crypto-header-logo.png and /dev/null differ diff --git a/frontend/src/assets/img/app-icons/crypto-header-logo.webp b/frontend/src/assets/img/app-icons/crypto-header-logo.webp new file mode 100644 index 0000000..1fe2223 Binary files /dev/null and b/frontend/src/assets/img/app-icons/crypto-header-logo.webp differ diff --git a/frontend/src/assets/img/app-icons/feather.svg b/frontend/src/assets/img/app-icons/feather.svg deleted file mode 100644 index 6f3a090..0000000 --- a/frontend/src/assets/img/app-icons/feather.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/feather.webp b/frontend/src/assets/img/app-icons/feather.webp new file mode 100644 index 0000000..fb96e3b Binary files /dev/null and b/frontend/src/assets/img/app-icons/feather.webp differ diff --git a/frontend/src/assets/img/app-icons/flare.svg b/frontend/src/assets/img/app-icons/flare.svg deleted file mode 100644 index 3cad880..0000000 --- a/frontend/src/assets/img/app-icons/flare.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/flare.webp b/frontend/src/assets/img/app-icons/flare.webp new file mode 100644 index 0000000..4999692 Binary files /dev/null and b/frontend/src/assets/img/app-icons/flare.webp differ diff --git a/frontend/src/assets/img/app-icons/house.svg b/frontend/src/assets/img/app-icons/house.svg deleted file mode 100644 index 050488a..0000000 --- a/frontend/src/assets/img/app-icons/house.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/house.webp b/frontend/src/assets/img/app-icons/house.webp new file mode 100644 index 0000000..575b2d2 Binary files /dev/null and b/frontend/src/assets/img/app-icons/house.webp differ diff --git a/frontend/src/assets/img/app-icons/local-pages.webp b/frontend/src/assets/img/app-icons/local-pages.webp index 91bd3b1..94d92fa 100644 Binary files a/frontend/src/assets/img/app-icons/local-pages.webp and b/frontend/src/assets/img/app-icons/local-pages.webp differ diff --git a/frontend/src/assets/img/app-icons/memos.webp b/frontend/src/assets/img/app-icons/memos.webp index 589e615..8bd9bd5 100644 Binary files a/frontend/src/assets/img/app-icons/memos.webp and b/frontend/src/assets/img/app-icons/memos.webp differ diff --git a/frontend/src/assets/img/app-icons/music.svg b/frontend/src/assets/img/app-icons/music.svg deleted file mode 100644 index 308c035..0000000 --- a/frontend/src/assets/img/app-icons/music.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/music.webp b/frontend/src/assets/img/app-icons/music.webp new file mode 100644 index 0000000..db954d0 Binary files /dev/null and b/frontend/src/assets/img/app-icons/music.webp differ diff --git a/frontend/src/assets/img/app-icons/neon-drop-source.png b/frontend/src/assets/img/app-icons/neon-drop-source.png deleted file mode 100644 index fb2efc7..0000000 Binary files a/frontend/src/assets/img/app-icons/neon-drop-source.png and /dev/null differ diff --git a/frontend/src/assets/img/app-icons/picstagram.webp b/frontend/src/assets/img/app-icons/picstagram.webp index 6512bb2..68dcd58 100644 Binary files a/frontend/src/assets/img/app-icons/picstagram.webp and b/frontend/src/assets/img/app-icons/picstagram.webp differ diff --git a/frontend/src/assets/img/app-icons/radio.webp b/frontend/src/assets/img/app-icons/radio.webp index bbce3fa..bc8fa54 100644 Binary files a/frontend/src/assets/img/app-icons/radio.webp and b/frontend/src/assets/img/app-icons/radio.webp differ diff --git a/frontend/src/assets/img/app-icons/sky-flappy.svg b/frontend/src/assets/img/app-icons/sky-flappy.svg deleted file mode 100644 index 2588494..0000000 --- a/frontend/src/assets/img/app-icons/sky-flappy.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/skyride.svg b/frontend/src/assets/img/app-icons/skyride.svg deleted file mode 100644 index 00e99ca..0000000 --- a/frontend/src/assets/img/app-icons/skyride.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/skyride.webp b/frontend/src/assets/img/app-icons/skyride.webp new file mode 100644 index 0000000..84eec27 Binary files /dev/null and b/frontend/src/assets/img/app-icons/skyride.webp differ diff --git a/frontend/src/assets/img/app-icons/weazel-news.svg b/frontend/src/assets/img/app-icons/weazel-news.svg deleted file mode 100644 index c98d991..0000000 --- a/frontend/src/assets/img/app-icons/weazel-news.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/weazel-news.webp b/frontend/src/assets/img/app-icons/weazel-news.webp new file mode 100644 index 0000000..17d140d Binary files /dev/null and b/frontend/src/assets/img/app-icons/weazel-news.webp differ diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 143de6c..e37c361 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -252,6 +252,13 @@ button { padding: 0 var(--phone-edge-gap, 24px) var(--phone-edge-gap, 24px) 0; pointer-events: none; } +.phone-stage--browser-preview { + place-items: center; + padding: 0; +} +.phone-stage--browser-preview .phone-device { + filter: none; +} .phone-stage--peek .phone-resolution-wrapper--primary .phone-device { transform: translateY(calc(100% - 190px)); transform-origin: right bottom; diff --git a/frontend/src/config/apps.ts b/frontend/src/config/apps.ts index bca3955..0cda34d 100644 --- a/frontend/src/config/apps.ts +++ b/frontend/src/config/apps.ts @@ -45,7 +45,7 @@ import appStoreIcon from '@/assets/img/app-icons/apps.webp' import calculatorIcon from '@/assets/img/app-icons/calculator.webp' import cameraIcon from '@/assets/img/app-icons/camera.webp' import clockIcon from '@/assets/img/app-icons/clock.webp' -import calendarIcon from '@/assets/img/app-icons/calendar.svg' +import calendarIcon from '@/assets/img/app-icons/calendar.webp' import mailIcon from '@/assets/img/app-icons/mail.webp' import mapIcon from '@/assets/img/app-icons/map.webp' import messagesIcon from '@/assets/img/app-icons/sms.webp' @@ -67,20 +67,20 @@ import weatherIcon from '@/assets/img/app-icons/weather.webp' import healthIcon from '@/assets/img/app-icons/health.webp' import bankingIcon from '@/assets/img/app-icons/banking.webp' import cryptoIcon from '@/assets/img/app-icons/crypto.webp' -import billingIcon from '@/assets/img/app-icons/billing.svg' +import billingIcon from '@/assets/img/app-icons/billing.webp' import garageIcon from '@/assets/img/app-icons/garage.webp' -import houseIcon from '@/assets/img/app-icons/house.svg' +import houseIcon from '@/assets/img/app-icons/house.webp' import citymarktIcon from '@/assets/img/app-icons/citymarkt.webp' import localPagesIcon from '@/assets/img/app-icons/local-pages.webp' -import flareIcon from '@/assets/img/app-icons/flare.svg' +import flareIcon from '@/assets/img/app-icons/flare.webp' import flipTokIcon from '@/assets/img/app-icons/fliptok.webp' import picstagramIcon from '@/assets/img/app-icons/picstagram.webp' -import skyRideIcon from '@/assets/img/app-icons/skyride.svg' -import musicIcon from '@/assets/img/app-icons/music.svg' -import featherIcon from '@/assets/img/app-icons/feather.svg' -import crewLinkIcon from '@/assets/img/app-icons/crewlink.svg' -import companiesIcon from '@/assets/img/app-icons/companies.svg' -import weazelNewsIcon from '@/assets/img/app-icons/weazel-news.svg' +import skyRideIcon from '@/assets/img/app-icons/skyride.webp' +import musicIcon from '@/assets/img/app-icons/music.webp' +import featherIcon from '@/assets/img/app-icons/feather.webp' +import crewLinkIcon from '@/assets/img/app-icons/crewlink.webp' +import companiesIcon from '@/assets/img/app-icons/companies.webp' +import weazelNewsIcon from '@/assets/img/app-icons/weazel-news.webp' import cityWarnIcon from '@/assets/img/app-icons/citywarn.webp' import type { BuiltinPhoneAppDefinition, diff --git a/frontend/src/utils/nui.ts b/frontend/src/utils/nui.ts index eecee7e..02a40e4 100644 --- a/frontend/src/utils/nui.ts +++ b/frontend/src/utils/nui.ts @@ -11,18 +11,21 @@ export async function nuiCall( endpoint: string, data: Record = {}, ): Promise> { - const developmentPort = new URLSearchParams(window.location.search).get( - 'apiPort', - ) - const baseUrl = import.meta.env.DEV - ? `http://localhost:${developmentPort ?? '3002'}/api` - : `https://${resourceName}` - const requestData = import.meta.env.DEV + const developmentParameters = new URLSearchParams(window.location.search) + const developmentPort = developmentParameters.get('apiPort') + const configuredApiBase = developmentParameters.get('apiBase') + const browserApiBase = configuredApiBase?.startsWith('/') + ? configuredApiBase.replace(/\/$/, '') + : null + const baseUrl = browserApiBase + ?? (import.meta.env.DEV + ? `http://localhost:${developmentPort ?? '3002'}/api` + : `https://${resourceName}`) + const requestData = browserApiBase || import.meta.env.DEV ? { ...data, _testScenario: - new URLSearchParams(window.location.search).get('testScenario') ?? - undefined, + developmentParameters.get('testScenario') ?? undefined, } : data const controller = new AbortController() diff --git a/frontend/src/views/apps/CryptoApp.vue b/frontend/src/views/apps/CryptoApp.vue index e99d5a8..1cd357f 100644 --- a/frontend/src/views/apps/CryptoApp.vue +++ b/frontend/src/views/apps/CryptoApp.vue @@ -25,7 +25,7 @@ import { WalletCards, } from 'lucide-vue-next' import { computed, onMounted, onUnmounted, ref, watch } from 'vue' -import cryptoHeaderLogo from '@/assets/img/app-icons/crypto-header-logo.png' +import cryptoHeaderLogo from '@/assets/img/app-icons/crypto-header-logo.webp' import CryptoLogo from '@/components/crypto/CryptoLogo.vue' import { useAccountStore } from '@/stores/account' import { useCryptoStore } from '@/stores/crypto' diff --git a/frontend/testserver/index.cjs b/frontend/testserver/index.cjs index c1b6eda..c70b2ab 100644 --- a/frontend/testserver/index.cjs +++ b/frontend/testserver/index.cjs @@ -2906,6 +2906,50 @@ let calendarEvents = [ title: 'Beach cruise', }, ] +const demoInstalledAppIds = [ + 'citywarn', + 'crypto', + 'health', + 'weazel-news', + 'companies', + 'music', + 'picstagram', + 'feather', + 'fliptok', + 'flare', + 'calendar', + 'radio', + 'local-pages', + 'crewlink', + 'phone', + 'messages', + 'darkchat', + 'garage', + 'house', + 'map', + 'skyride', + 'banking', + 'billing', + 'mail', + 'notes', + 'memos', + 'calculator', + 'camera', + 'clock', + 'weather', + 'photos', + 'app-store', + 'settings', + 'snake', + 'memory', + 'number-merge', + 'minesweeper', + 'tower-stack', + 'sky-flappy', + 'citymarkt', + 'neon-drop', +] +const demoWallpaperUrl = '/img/wallpapers/sky-phone-demo-gradient.png' const deviceData = { appAuth: { payload: { @@ -2949,7 +2993,7 @@ const deviceData = { }, apps: { payload: { - claimedApps: [], + claimedApps: [...demoInstalledAppIds], }, revision: 2, }, @@ -3042,6 +3086,8 @@ const deviceData = { airplaneMode: false, appearanceMode: 'automatic', frame: 'black', + lockWallpaper: 'custom', + lockWallpaperImageUrl: demoWallpaperUrl, notificationDurationSeconds: 10, notificationSound: 'chime', notificationVolume: 70, @@ -3049,7 +3095,11 @@ const deviceData = { ringtone: 'skyline', ringtoneVolume: 80, streamerMode: false, - wallpaper: 'midnight', + wallpaper: 'custom', + wallpaperHistory: [ + { imageUrl: demoWallpaperUrl, wallpaper: 'custom' }, + ], + wallpaperImageUrl: demoWallpaperUrl, }, version: 1, }, @@ -9716,7 +9766,33 @@ app.post('/api/:endpoint', (request, response) => { }, }, } - : deviceData + : testScenario === '' + ? { + ...deviceData, + apps: { + ...deviceData.apps, + payload: { + claimedApps: [...demoInstalledAppIds], + }, + }, + settings: { + ...deviceData.settings, + payload: { + ...deviceData.settings.payload, + settings: { + ...deviceData.settings.payload.settings, + lockWallpaper: 'custom', + lockWallpaperImageUrl: demoWallpaperUrl, + wallpaper: 'custom', + wallpaperHistory: [ + { imageUrl: demoWallpaperUrl, wallpaper: 'custom' }, + ], + wallpaperImageUrl: demoWallpaperUrl, + }, + }, + }, + } + : deviceData response.json({ success: true, data: { diff --git a/frontend/testserver/smoke.cjs b/frontend/testserver/smoke.cjs index 73a8129..fb00c9e 100644 --- a/frontend/testserver/smoke.cjs +++ b/frontend/testserver/smoke.cjs @@ -109,6 +109,31 @@ function verifyBrowserTestData(dataByEndpoint) { 'camera captures', 2, ) + expectItems( + development.device.data.apps.payload.claimedApps, + 'installed demo apps', + 41, + ) + assert.equal( + new Set(development.device.data.apps.payload.claimedApps).size, + 41, + 'browser demo apps were not uniquely installed', + ) + assert.equal( + development.device.data.settings.payload.settings.wallpaper, + 'custom', + 'browser demo home wallpaper was not configured', + ) + assert.equal( + development.device.data.settings.payload.settings.lockWallpaper, + 'custom', + 'browser demo Lock Screen wallpaper was not configured', + ) + assert.match( + development.device.data.settings.payload.settings.wallpaperImageUrl, + /sky-phone-demo-gradient\.png$/, + 'browser demo wallpaper image was not configured', + ) expectItems(development.memos, 'memos', 3) expectItems(development.notes, 'notes', 4) assert(