diff --git a/.biome/README.md b/.biome/README.md new file mode 100644 index 0000000..7d9a5a0 --- /dev/null +++ b/.biome/README.md @@ -0,0 +1,25 @@ +# `.biome/` + +Custom lint rules for the farish codebase, written as +[Biome GritQL plugins][grit]. + +Biome ships 200+ built-in rules; `biome.json` turns the strict ones on. This +directory holds **farish-specific** rules — patterns we want banned that no +built-in rule covers. + +## Plugins + +| Plugin | Bans | +| --------------------------- | ------------------------------------------------------- | +| `plugins/no-as-any.grit` | `x as any` casts — they silently disable type-checking. | + +## Adding a rule + +1. Write a `.grit` file under `plugins/`. +2. Register it in the `plugins` array of `biome.json`. +3. Run `mise run lint` to confirm it loads and fires. + +See [`docs/monorepo/lint-format.md`](../docs/monorepo/lint-format.md) for the +full lint/format guide and the rationale for choosing Biome. + +[grit]: https://biomejs.dev/linter/plugins/ diff --git a/.biome/plugins/no-as-any.grit b/.biome/plugins/no-as-any.grit new file mode 100644 index 0000000..1a5c346 --- /dev/null +++ b/.biome/plugins/no-as-any.grit @@ -0,0 +1,18 @@ +// no-as-any.grit — custom Biome lint rule (GritQL plugin). +// +// Bans the `x as any` escape hatch anywhere in the farish codebase. `as any` +// silently disables type-checking for an expression and is the single most +// common way strictness is defeated. The built-in `noExplicitAny` rule catches +// bare `any` annotations; this plugin closes the cast-shaped hole it leaves. +// +// Pattern we do NOT want: const v = something as any; +// Do this instead: narrow with a type guard, or cast to `unknown` +// first and then to a precise type. +// +// Docs: docs/monorepo/lint-format.md +`$expr as any` where { + register_diagnostic( + span = $expr, + message = "farish/no-as-any: `as any` defeats type-checking — narrow the type or cast through `unknown` to a precise type instead." + ) +} diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..899954e --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# SessionStart hook for Claude Code on the web. +# +# Onboards the `farish` repo: installs the pinned toolchain and workspace +# dependencies so linters, tests and builds work inside a web session. +# +# It is intentionally idempotent and forward-looking: the repo starts with no +# code, so each install step is guarded by the presence of its manifest. As the +# monorepo is scaffolded (mise.toml, package.json, ...) the matching steps begin +# to run automatically with no edit to this hook required. +set -euo pipefail + +cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" + +echo "[session-start] onboarding farish in ${PWD}" + +# Toolchain install (mise) is handled by the mise plugin's own SessionStart +# hook — intentionally NOT duplicated here. + +# bun — install workspace dependencies once a manifest exists. bun is provided +# by mise (pinned in mise.toml), so route through `mise exec` so bun resolves +# even before mise's shims reach PATH on a first run — rather than assuming bun +# is already on PATH. Project-specific; a future bun-utils plugin could own it. +if [ -f package.json ]; then + if command -v mise >/dev/null 2>&1; then + echo "[session-start] bun install (via mise exec)" + mise exec -- bun install || echo "[session-start] bun install failed (continuing)" + elif command -v bun >/dev/null 2>&1; then + echo "[session-start] bun install" + bun install || echo "[session-start] bun install failed (continuing)" + else + echo "[session-start] package.json present but neither mise nor bun on PATH — skipping" + fi +fi + +# Keep local-checkout plugin marketplaces fresh. farish's `agents-local` and +# `ai-mktpl-local` marketplaces are `directory` sources pointing at local git +# checkouts; a directory marketplace serves whatever is in the checkout's +# working tree, so each checkout must be pulled forward to stay current with +# its branch. Best-effort — failures never abort onboarding. +for repo in /home/user/agents /home/user/ai-mktpl; do + if [ -d "$repo/.git" ]; then + echo "[session-start] refreshing local marketplace checkout: $repo" + git -C "$repo" fetch --quiet origin || echo "[session-start] fetch failed for $repo (continuing)" + git -C "$repo" pull --ff-only --quiet || echo "[session-start] pull skipped/failed for $repo (continuing)" + fi +done + +echo "[session-start] done" +exit 0 diff --git a/.claude/plugins.settings.yaml b/.claude/plugins.settings.yaml new file mode 100644 index 0000000..a92b96c --- /dev/null +++ b/.claude/plugins.settings.yaml @@ -0,0 +1,22 @@ +# Plugin settings for the farish project. +# Schema: each top-level key is a plugin name; see that plugin's skill docs. + +1pass: + enabled: true + # Download the 1Password CLI (`op`) when not on PATH. The session provides + # OP_SERVICE_ACCOUNT_TOKEN (vault: AI-Jack), so `op` auto-authenticates. + autoInstall: true + # Inject the GitHub App credentials from 1Password so the github-app plugin + # can mint an installation token on SessionStart. Item: github--app--jack. + secrets: + - envVar: GITHUB_APP_ID + reference: "op://AI-Jack/github--app--jack/GITHUB_APP_ID" + - envVar: GITHUB_INSTALLATION_ID + reference: "op://AI-Jack/github--app--jack/GITHUB_INSTALLATION_ID" + - envVar: GITHUB_APP_PRIVATE_KEY + reference: "op://AI-Jack/github--app--jack/GITHUB_APP_PRIVATE_KEY" + +github-app: + enabled: true + # Configure git identity to the GitHub App bot user automatically. + autoGitConfig: true diff --git a/.claude/rules/always-keep-improving.md b/.claude/rules/always-keep-improving.md new file mode 100644 index 0000000..829ac40 --- /dev/null +++ b/.claude/rules/always-keep-improving.md @@ -0,0 +1,122 @@ +# Always keep improving + +1. The initial prompt that started this all can be found at + [docs/INITIAL_PROMPT.md](../../docs/INITIAL_PROMPT.md) and should be reviewed + to ensure progress is being made accurately. + +You can do literally anything. The skills you will make and have made make what +you do possible. To do something new, all you need to do is make a skill. Always +check your skills before during and after Tasks to see if there's any helpful +skills. Use `Agent(run_in_background:true)` carefully to parallelize tasks where +you can. These skills are only as good as their instructions. + +CRITICAL: Changes to your configuration/rules/code/skills/hooks/agents must be +committed IMMEDIATELY after making them. + +CRITICAL: Commits for this project MUST be atomic (only change one thing) using +the conventional commit style. Push often. Use CI to your advantage, but local +is always faster. + +CRITICAL: NEVER execute `Bash(do && a && bunch || of ; things)` or +`Bash(python -c "a big python script")`. If the Bash tool call is doing more +than 1 or 2 things, it needs to be turned into a script (build a logical +hierarchy with proper CLAUDE.md documentation throughout in +`.claude/scripts/paths/to/categorize/meaningful-name.sh`. ALWAYS check to see if +a script exists before creating a new one. ALWAYS know which scripts you'll need +before executing a plan. ALWAYS make sure the scripts are tested. ALWAYS share +code between scripts when possible. ALWAYS prefer bun/ts/compiled tool (but +scripts are okay). ALWAYS use monorepo tooling, even in the scripts, they're +code too! + +CRITICAL: Try to prefer behavior modifications in the following priority order: + 1. Hooks in claude configs (remember don't keep any in settings.local.json + they're not saved!) + 2. `Skill(context:fork)` + 3. `Agent()` - don't forget they're resumable! Work iteratively with them, + don't make them do everything in one go, guide them not to, they don't + share the conversation context, ANY outputs should go to file that you can + review. ALWAYS run them in the background AND check their work. + - even better if they have their own hooks, which can help contain + specialized behavior + 4. Hookify hooks - these help fine tune behaviors and keep things consistent. + Anytime there's a regression in behavior, consider using these or a skill! + 5. Skills still, with scripts to encapsulate behavior + 6. Scripts + 7. Rules. These take up context and aren't really actually enforceable. +Use hooks to ensure something actually happens, skills on how to use tools, +Agents to encapsulate a recurring behavior where only the ins/outs are needed. + +CRITICAL: NEVER use the general-purpose agent. ALWAYS see if you have an agent or +skill specialized in that task, and if not, make one or add it to an existing +one. + +CRITICAL: KEEP SKILLS SMALL. Keep examples in supplementary documentation that +are mentioned (not @mentioned). Make most skills an ordered list of +instructions. Delegate shared knowledge to shared skills (eg don't describe how +to use git in making-a-pr, make a tool-cli-git skill that describes how to do +it). Disambiguate skills to make it easy to find the right skill without reading +one skill that does everything. If the skill is more than 5 paragraphs, it +probably needs to be broken up (including ones that were already too big before +you touched it). Always break things into maintainable and shareable chunks. + +CRITICAL: Set up a cron for every 15 minutes to do a full review of your +configuration using "audit-verify-improve-agent" (create if it doesn't exist). +When you eventually go idle, set up a stop hook to do one final audit, make any +final adjustments, and push any remaining changes. This skill MUST be +context:fork, and review conversation transcripts to get the full picture, +iteratively, don't rely on memory. Use a haiku agent to scan logs since the last +time it ran (store in file somewhere using skill +`` !`date | tee .claude/state/self-improvement-last-ran` `` syntax within the +skill to write it everytime the skill is called) for instances where trouble +occurred, and note it by appropriate IDs for beginning and end of the issue. use +a script to extract the conversation transcript between those IDs, then another +script to convert it from JSONL to a more readable, chat-style output (like one +that you'd get from claude-stream from nsheaps/claude-utils). + +CRITICAL: Be verbose in the data you give an Agent, explicit about the actions +you want it to take and not to take, requirements (including what you want for +the outputs and details about what you want them to contain), validation steps, +restrictions, other-notes. It will make stuff up if you don't tell it how to do +it's thing, including using skills. Use resuming to your advantage. Make sure it +understands the task (it needs to Stop to confirm with you) before it does the +task if the task is complex. Check it's outputs, don't always assume they're +right. + +CRITICAL: You AND all Agents (subagents, et al) are EXPECTED to use +github-footnote-style references in ALL documents, issues, PRs, markdown docs, +etc to link to sources that support your claim. Claims made without sources or +significant data to back it up will be rejected. Use the repo's +`.claude/sources/xxx/yyy/zzz.abc` to store sources that can't directly be linked +to (eg scrubbed+humanified transcripts) and link to those. Always reference +files inside the repo using `github.com/…` links, NEVER filesystem paths. Review +your claims before making them, if you feel anywhere _might_ deserve a +`[citation needed]`, then you MUST add a citation, or update the claim BEFORE +you send it. + +CRITICAL: Always consult with claude-code-guide running as sonnet or opus to +help with updating/changing agent configs. Encourage it to always be thorough in +it's research. Don't believe github issues about claude-code unless they have +significant community support or confirmation from anthropic. + +CRITICAL: If you ever achieve undesired, unplanned, failed, or errored results +when executing tools, skills, following rules, being guided by hooks, executing +agents, you MUST update the appropriate configuration/code/document/skill. + +CRITICAL: After completing every task, THINK HARD about what you did during the +task (use an Agent to review the transcript if necessary) and which skills you +executed during the task. Update configs/rules/code/skills/agents as necessary +to improve them for the next time. + +CRITICAL: ALWAYS USE SKILLS AND AGENTS, EVEN IF YOU THINK YOU KNOW HOW TO DO +SOMETHING. + +CRITICAL: ALWAYS use `/scm-utils:commit` to commit code. + +CRITICAL: images use up insane amounts of context. ONLY use them within an +`Agent()`. Be explicit about what you want to know about the image. + +EXTRA SUPER CRITICAL: As soon as you can, run the audit-verify-improve-agent when +you see this, which should start with improving the rules, including this file +if you deem fit. Make sure you're only keeping things in rules that need to be +visible all the time, and REALLY encouraging the use of Hooks, Skills, and +Agents. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..b584773 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "defaultMode": "bypassPermissions" + }, + "env": { + "TASK_UTILS_TASK_DIR": "/home/user/farish/.claude/tasks", + "CLAUDE_CODE_ENABLE_TASKS": "1", + "CLAUDE_CODE_ENABLE_BACKGROUND_PLUGIN_REFRESH": "1", + "CLAUDE_AUTO_BACKGROUND_TASKS": "1", + "CLAUDE_CODE_DISABLE_CRON": "0", + "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS": "0", + "CLAUDE_CODE_EXIT_AFTER_STOP_DELAY": "60000", + "FORCE_AUTOUPDATE_PLUGINS": "1" + }, + "extraKnownMarketplaces": { + "agents": { + "source": { + "source": "github", + "repo": "nsheaps/agents" + } + }, + "ai-mktpl": { + "source": { + "source": "github", + "repo": "nsheaps/ai-mktpl" + } + }, + "agents-local": { + "source": { + "source": "directory", + "path": "/home/user/agents" + } + }, + "ai-mktpl-local": { + "source": { + "source": "directory", + "path": "/home/user/ai-mktpl" + } + } + }, + "enabledPlugins": { + "cron-utils@agents-local": true, + "task-utils@agents-local": true, + "shared-lib@ai-mktpl-local": true, + "dangerous-bypass@ai-mktpl-local": true, + "agentic-behavior@ai-mktpl-local": true, + "common-sense@ai-mktpl-local": true, + "deep-research@ai-mktpl-local": true, + "edit-utils@ai-mktpl-local": true, + "github@ai-mktpl-local": true, + "github-app@ai-mktpl-local": true, + "1pass@ai-mktpl-local": true, + "mise@ai-mktpl-local": true, + "scm-utils@ai-mktpl-local": true, + "sequential-thinking@ai-mktpl-local": true, + "hookify@claude-plugins-official": true, + "plugin-dev@claude-plugins-official": true + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + } +} diff --git a/.claude/skills/build-confidence-by-trying-to-prove-incorrect/SKILL.md b/.claude/skills/build-confidence-by-trying-to-prove-incorrect/SKILL.md new file mode 100644 index 0000000..5d5c653 --- /dev/null +++ b/.claude/skills/build-confidence-by-trying-to-prove-incorrect/SKILL.md @@ -0,0 +1,41 @@ +--- +name: build-confidence-by-trying-to-prove-incorrect +description: Resolve a decision, claim, or plan you are not fully confident in by dispatching an adversarial Agent whose job is to disprove it. Use before committing to a non-trivial or hard-to-reverse next step while ambiguity or low confidence remains. Trigger phrases — "I have a feeling but want to be sure", "is this the right next step?", "prove this wrong before I act", "I keep wanting to second-guess this". +context: fork +--- + +# Build confidence by trying to prove it incorrect + +When you have a hypothesis, plan, or next step you are not fully confident in, +do not guess and do not proceed. Dispatch an adversarial `Agent()` whose +explicit job is to **disprove** it. Proceed only once the claim survives a +genuine attack. + +## Steps + +1. Write the claim/plan as a single falsifiable statement, plus the concrete + next step it would justify and why the step is hard to reverse. +2. Dispatch `Agent(run_in_background: true)` with an adversarial brief: its job + is to prove the claim wrong, unsafe, or that a better option exists — NOT to + agree. Instruct it to verify empirically wherever possible, cite official or + primary sources, and to state explicitly if it cannot disprove the claim. +3. Require the agent to write its findings to a file and return a short + verdict: which claims survived, which were disproven, and the corrected + next step. +4. If a claim is disproven, adopt the correction. If confidence is still not + sufficient, repeat from step 1 on the corrected claim. +5. If the claim survives a genuine attack, confidence is sufficient — proceed. + +## Stop condition + +You are done debating when you no longer feel you would need another round to +prove the next step wrong. Lingering "do nothing until proven correct" +hesitation means another round is needed. + +## Notes + +- The adversary must genuinely attack — an agreeable reviewer gives false + confidence. Frame the brief so agreement is failure. +- Empirical disproof beats argument: prefer an agent that can run a test. +- This is for uncertainty, not for every decision — skip it when the next step + is obvious and easily reversible. diff --git a/.claude/skills/do-some-work/SKILL.md b/.claude/skills/do-some-work/SKILL.md new file mode 100644 index 0000000..b0cf2d4 --- /dev/null +++ b/.claude/skills/do-some-work/SKILL.md @@ -0,0 +1,72 @@ +--- +name: do-some-work +description: > + End-to-end workflow for implementing a Task: pick up a task from + .claude/tasks/, implement it, validate, commit, push, and confirm CI is + green. Use when starting work, "pick up a task", "do some work", + "implement task N", "work on the next task", or resuming in-progress work. +--- + +# Do Some Work + +Sequential checklist for delivering a Task end-to-end. Each phase builds on +the last — do not skip steps. + +## Steps + +1. **Pick up the task.** + - `ls .claude/tasks/` — find the next `todo` task. Read its JSON. + - If creating a new record: `{"id":"...","subject":"...","status":"todo","description":"..."}`. + - Update `status` to `"in_progress"`, write back to `.claude/tasks/.json`. + - Re-read `docs/INITIAL_PROMPT.md` to confirm the task maps to the right step number. + +2. **Plan the implementation.** + - Run `Skill(sequential-thinking:sequential-thinking)` to break the change into atomic commits before touching any file. + - If the task is a spec or page: run `Skill(spec-writing)` first. + - If the task needs wireframes: run `Skill(page-wireframing)`. + - If the task produces API requirements: run `Skill(page-to-api-requirements)`. + - Note which skills apply before writing code. + +3. **Implement incrementally.** + - One logical change per commit (atomic conventional commits — `always-keep-improving.md`). + - After each file change, update the task record with a short progress note. + - Never chain Bash commands; turn multi-step operations into scripts. + +4. **Write or update automated tests.** + - New behaviour must have tests in the same commit that introduces it. + - Playwright tests cover every page; tests must be named to match the page spec. + +5. **Validate locally.** + - Run `Skill(validate-change)` — it routes to the right subskill(s) for your change type. + - Root gate: `mise run check` (lint → test → build). This is the exact sequence CI runs. + - Fix every failure before committing. + +6. **Commit and push.** + - Run `Skill(scm-utils:commit)` for each atomic commit. + - Push: `git push origin HEAD`. + +7. **Verify CI — standing step-28 requirements.**[^step28] + - Run `Skill(validate-app-progress)` — it enumerates and checks every + standing requirement: CI green, CI codepaths == local tooling, + screenshots of every page on `main` (`[skip ci]`, date-based path, + published to Pages), and specs/docs published to the Pages docs site. + - All CI checks must be green. If any fail: fix locally (step 5), re-commit, re-push. + - No PR is considered done until the CI run on that PR's branch is fully green. + +8. **Complete the task.** + - Update the task record: `"status": "completed"`. + - Capture any follow-ups as new `.claude/tasks/.json` records or GitHub issues. + - Run `Skill(validate-change-skill)` if any skill or rule was modified during this task. + - After completing the task, re-read `docs/INITIAL_PROMPT.md` and confirm the step is done. + +## Notes + +- Task records live in `.claude/tasks/.json`. The task-utils MCP server + auto-commits them when available; otherwise write them by hand. +- Keep commits atomic: one reason to change, one commit. Use `feat:`, `fix:`, + `docs:`, `chore:`, `ci:` prefixes.[^conventional] +- CI screenshot publishing and spec publishing are defined in step 28 of the + initial prompt and must be validated every time a workflow changes. + +[^step28]: Step 28 of `docs/INITIAL_PROMPT.md` — CI always green, same codepaths as local, screenshots of every page on main committed with `[skip ci]` and published to GitHub Pages, specs published to GitHub Pages. See [INITIAL_PROMPT.md](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/docs/INITIAL_PROMPT.md#L307). +[^conventional]: Conventional Commits — diff --git a/.claude/skills/page-to-api-requirements/SKILL.md b/.claude/skills/page-to-api-requirements/SKILL.md new file mode 100644 index 0000000..f08665f --- /dev/null +++ b/.claude/skills/page-to-api-requirements/SKILL.md @@ -0,0 +1,90 @@ +--- +name: page-to-api-requirements +description: > + Extract API requirements from farish page specs and wireframes into + docs/api//SPEC.md files. Use when asked to "extract API + requirements", "identify endpoints", "map pages to API", "design the REST + API", or when working through prompt step 18. Processes each page spec's + remote data items and backend interactions, decides REST vs WebSocket, and + records each endpoint in a canonical SPEC.md. +--- + +# Page-to-API Requirements + +Extracts every backend endpoint farish needs from the page specs and +wireframes, then records each one in `docs/api//SPEC.md`. +Designed so prompt step 18 is "for each page in INDEX.md, run this skill". + +Read `docs/api/API-SPEC.md` for the required shape of each endpoint spec — this +skill does not inline those rules. + +## Steps + +1. Read `docs/pages/INDEX.md`. Note every page tagged `backend` or + `backend_dependent: partial | yes` — these are the primary sources of + remote requirements. Pages tagged `browser-only` may still contribute + write actions (Share, Rate) that need backend endpoints. + +2. Read `docs/api/API-SPEC.md` — internalize the required frontmatter, + section order, and format rules before writing any endpoint spec. + +3. For each page in INDEX.md, read its `docs/pages//SPEC.md`. Extract + two lists: + - **Remote data items** — every entry in `## Data` marked `remote`. + Each item maps to at least one endpoint (a read GET, a write POST/PUT, + or both). + - **Backend interactions** — every entry in `## Interactions` that + triggers a server-side effect: publish, rate, view-count increment, + search with server results, paginated fetch, user-profile lookup. + +4. Collect requirements across all pages and group them into distinct + endpoint records — one record per unique operation on a resource. + `GET /models` (list/search) and `POST /models` (create) are separate + endpoints even though they share a route. + +5. For each endpoint record, choose the transport: + + **Use REST** when the operation is request-response: fetch a resource, + search/filter/paginate, submit a mutation, increment a counter. All + endpoints in farish's current social layer are REST. + + **Use WebSocket** only when the server needs to push a sequence of + asynchronous messages over a long-lived connection: real-time generation + progress, collaborative editing, live notifications. Reserve WebSocket + for future server-side features. + + > **Note — Generate page**: the Generate page's AI calls go directly to + > the Claude API from the browser using the user's own key via the + > Claude Agent SDK. That stream is client-side only and does not require + > a farish WebSocket. Mark the `generationStream` data item as + > `browser-only` and exclude it from endpoint extraction.[^1] + +6. For each endpoint, create `docs/api//SPEC.md` following + `docs/api/API-SPEC.md`. Record transport, method/route, request shape + (path params, query params, body), response body and status codes, auth + requirement, error cases, and which source page specs require it. Create + the parent directory `docs/api//` if missing. + +7. Commit related endpoint specs atomically: + `docs(api): add endpoint specs` + Push after each resource group (models, users, leaderboards, etc.). + +## Notes + +- One `SPEC.md` per distinct operation — `GET /models` and `POST /models` + get separate spec files. +- Keep each endpoint spec under ~100 lines (API-SPEC.md rule). If a + resource has many operations, describe each in its own file rather than + combining them. +- The `docs/api/` directory may not exist on first run; create it before + writing the first spec. +- Steps 19–20 of the initial prompt are review loops on the output of this + skill; keep specs in `draft` status until those passes complete. + +## References + +[^1]: Initial prompt step 17 — "You may use a websocket where necessary to + provide realtime 2-way streams (eg if you want for the agent loop + displaying steps in the browser)" — the current browser-only + architecture satisfies this with the Claude Agent SDK client-side. + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). diff --git a/.claude/skills/page-wireframing-ascii/SKILL.md b/.claude/skills/page-wireframing-ascii/SKILL.md new file mode 100644 index 0000000..213de17 --- /dev/null +++ b/.claude/skills/page-wireframing-ascii/SKILL.md @@ -0,0 +1,67 @@ +--- +name: page-wireframing-ascii +description: > + Stage 1 of wireframing: produce ASCII art wireframes for a farish page and + each of its modals/popovers. Use when asked to "draw the ASCII wireframe", + "sketch the page layout", or from page-wireframing skill step 4. +--- + +# Wireframing — ASCII Stage + +Produces rough ASCII art wireframes. One file for the full page; one file per +modal or popover. Files go in `docs/pages//wireframes/`. + +## Layout vocabulary + +Use this vocabulary consistently across all pages so wireframes look coherent: + +``` +┌─────────────────────────────────────────────────┐ +│ NAVBAR [Logo] [Generate] [Explore] [≡] │ ← VAppBar (full width) +├─────────────────────────────────────────────────┤ +│ (page body — varies per page) │ +│ │ +│ SIDEBAR │ MAIN CONTENT │ ← VNavigationDrawer + VMain +│ ┌─────────────┐ │ ┌─────────────────────┐ │ +│ │ filter / │ │ │ card grid / viewer │ │ ← VCard, VRow/VCol +│ │ params panel│ │ │ │ │ +│ └─────────────┘ │ └─────────────────────┘ │ +│ │ [ action bar ] │ ← VToolbar / VBtn +├─────────────────────────────────────────────────┤ +│ (no footer — single-page app) │ +└─────────────────────────────────────────────────┘ +``` + +Box-drawing chars: `┌ ┐ └ ┘ │ ─ ├ ┤ ┬ ┴ ┼`. Use `[ Button ]` for buttons, +`( radio )` for radio inputs, `[x]` for checkboxes, `[___field___]` for text +inputs, `★★★☆☆` for ratings, `▓▓▓▓░` for progress. + +## Steps + +1. Read `docs/pages//SPEC.md` — collect all components from `## Components` + and all states from `## States`. + +2. Read `docs/research/design-system-choice.md` — note the Vuetify component + for each spec component so the ASCII labels match implementation reality. + +3. Create `docs/pages//wireframes/page.ascii.md`. Draw the full-page + wireframe at desktop width (~80 chars wide). Rules: + - Show the NavBar at the top on every page. + - Depict every component listed in `## Components` — place them in the + layout region described in `## Layout` of the spec. + - Label each region with its component name and its Vuetify equivalent + in parentheses, e.g. `FILTER PANEL (VNavigationDrawer)`. + - Show the `default` state. Add a short note below the diagram for each + non-default state that changes the layout significantly. + +4. For each modal, popover, or overlay named in `## Components` or + `## Interactions` of the spec, create a separate file: + `docs/pages//wireframes/.ascii.md`. + Draw the modal at ~60 chars wide, centered, with a dimmed-backdrop indicator. + +5. If the page has a `coming-soon` state, create + `docs/pages//wireframes/coming-soon-overlay.ascii.md` showing the + ghost-wireframe-behind-overlay layout. + +6. Save all files. Do not proceed to stage 2 until every component from the + spec has a visible representation in the wireframes. diff --git a/.claude/skills/page-wireframing-image/SKILL.md b/.claude/skills/page-wireframing-image/SKILL.md new file mode 100644 index 0000000..452857f --- /dev/null +++ b/.claude/skills/page-wireframing-image/SKILL.md @@ -0,0 +1,64 @@ +--- +name: page-wireframing-image +description: > + Stage 2 of wireframing: produce a Mermaid-based visual wireframe for a farish + page (more detailed than ASCII; renders as an image on GitHub). Use when asked + to "create the visual wireframe", "draft the image wireframe", or from + page-wireframing skill step 5. +--- + +# Wireframing — Image (Mermaid) Stage + +Produces a detailed Mermaid diagram wireframe — the "drafty image-based version" +from initial prompt step 15. Uses Mermaid `block-beta` or `flowchart` syntax so +the diagram renders natively on GitHub and GitHub Pages (amendment A6).[^mermaid] + +Output file: `docs/pages//wireframes/page.mermaid.md` + +## Steps + +1. Read the completed ASCII wireframe at + `docs/pages//wireframes/page.ascii.md` — use it as the spatial + reference for component placement. + +2. Read `docs/pages//SPEC.md` sections `## Layout` and `## Components` + for the authoritative region hierarchy. + +3. Read `docs/research/design-system-choice.md` — confirm Vuetify component + names for each region so labels in the Mermaid diagram are implementation- + accurate. + +4. Write `docs/pages//wireframes/page.mermaid.md`. The file must contain: + - A heading: `# — Visual Wireframe` + - One Mermaid `flowchart TB` (or `block-beta`) diagram that shows: + - Every component from `## Components` as a labelled node. + - Layout regions (NavBar at top, sidebar left of main, action bar at bottom) + using subgraphs to group related nodes. + - Arrows indicating primary user flow through the page. + - A short "Component key" table below the diagram mapping each label to its + Vuetify equivalent (copied from `design-system-choice.md`). + - One paragraph noting which states change the diagram significantly. + +5. Keep the Mermaid diagram under ~40 nodes. If the page has enough modals to + warrant it, produce a second diagram for the primary modal in the same file + under a `## Modal` heading. + +6. Validate the diagram renders without syntax errors. Mermaid syntax rules: + - Node IDs must be alphanumeric or underscored. + - Subgraph labels in quotes if they contain spaces. + - Arrows: `-->` (flow) or `---` (association, no arrowhead). + +7. Save the file. + +## Notes + +- This diagram is "drafty" — it does not need to be pixel-perfect. Its purpose + is to validate that every component has a logical place in the layout. +- A6 requires Mermaid over images where a diagram is sufficient. If a component + cannot be meaningfully represented in Mermaid (e.g. the 3D WebGL canvas), use + a rectangular node labelled `[ThreeDViewer — custom WebGL canvas]`. + +## References + +[^mermaid]: Mermaid — renders natively on GitHub and GitHub Pages — + diff --git a/.claude/skills/page-wireframing-review/SKILL.md b/.claude/skills/page-wireframing-review/SKILL.md new file mode 100644 index 0000000..3a6150f --- /dev/null +++ b/.claude/skills/page-wireframing-review/SKILL.md @@ -0,0 +1,52 @@ +--- +name: page-wireframing-review +description: > + Stage 3 of wireframing: review all wireframes for a page against its spec + and identify gaps or inconsistencies. The "one more round of improvement" from + initial prompt step 15. Use when asked to "review the wireframes", "check + wireframes against spec", or from page-wireframing skill step 6. +--- + +# Wireframing — Review Stage + +Validates all wireframes for a page against the page spec and produces a +pass/fail checklist. One iteration of improvement is mandatory per prompt +step 15; iterate until all items pass. + +## Steps + +1. Read `docs/pages//SPEC.md` in full. + +2. List all wireframe files in `docs/pages//wireframes/`. Confirm: + - `page.ascii.md` exists (stage 1, full page). + - `page.mermaid.md` exists (stage 2, visual draft). + - One ASCII file per modal, popover, or overlay named in `## Components`. + - `coming-soon-overlay.ascii.md` exists if the spec has a `coming-soon` state. + +3. **Component coverage check.** For every component in `## Components` of the + spec, confirm it appears in both the ASCII and Mermaid wireframes. + Flag any component that is missing. + +4. **State coverage check.** For each state in `## States`, confirm: + - The default state is fully depicted. + - Each significant non-default state has at least a note in `page.ascii.md` + or a separate diagram if it substantially changes the layout. + +5. **Spec alignment check.** Compare the layout in the wireframes against + `## Layout` in the spec (the Mermaid flowchart or ASCII sketch). Flag any + region described in the spec that is absent from the wireframes. + +6. **Design-system consistency check.** Read + `docs/research/design-system-choice.md`. Confirm each component label in + the wireframes corresponds to a Vuetify component in the vocabulary table. + Flag any label that is ambiguous or unmapped. + +7. **Cross-page consistency check.** The NavBar must appear on every page + wireframe. Cards displaying model data must use the `ModelCard` label. + Flag violations. + +8. **Produce a checklist report** — PASS or FAIL for each item above. + For each FAIL, state which file is affected and what is missing. + +9. For each FAIL, update the relevant wireframe file(s), then re-run steps 2–8. + Continue iterating until all items PASS. diff --git a/.claude/skills/page-wireframing/SKILL.md b/.claude/skills/page-wireframing/SKILL.md new file mode 100644 index 0000000..65a0192 --- /dev/null +++ b/.claude/skills/page-wireframing/SKILL.md @@ -0,0 +1,51 @@ +--- +name: page-wireframing +description: > + Draft wireframes for a farish page. Use when asked to "wireframe the X page", + "create wireframes for X", "start wireframing", or when working through prompt + step 15. Runs the three-stage process: ASCII sketch → Mermaid visual draft → + one review/improvement round. Produces at least one whole-page wireframe plus + one per modal or popover. +--- + +# Page Wireframing + +Three-stage wireframing process for a single farish page. Designed so prompt +step 15 is "for each page in INDEX.md, run this skill". + +Design system: **Vuetify 3**. Component vocabulary is in +`docs/research/design-system-choice.md` — read it before starting. + +## Steps + +1. Read `docs/pages//SPEC.md` in full. Note every component, modal, + popover, and state in `## Components` and `## States`. + +2. Read `docs/research/design-system-choice.md` — confirm the Vuetify component + that maps to each component named in the spec. + +3. Create the directory `docs/pages//wireframes/` if it does not exist. + +4. Run `Skill(page-wireframing-ascii)` to produce the ASCII wireframe(s) — + one for the full page plus one per modal or popover identified in step 1. + +5. Run `Skill(page-wireframing-image)` to produce the Mermaid visual wireframe + for the full page (and modals, if complex enough to warrant it). + +6. Run `Skill(page-wireframing-review)` to validate all wireframes against the + page spec and identify gaps. + +7. For each gap found in step 6, update the affected wireframe file(s), then + re-run step 6. Iterate until the review passes with no remaining gaps. + +8. Commit the wireframes atomically: + `docs(wireframes): add page wireframes` + +## Notes + +- Wireframe files live in `docs/pages//wireframes/`. Never place them + elsewhere. +- The ASCII stage is stage 1; the Mermaid stage is stage 2; the review is the + "one more round of improvement" from the initial prompt (step 15). +- If a page has a `coming-soon` state (backend-gated), include a wireframe of + the ghost overlay state as a separate file: `coming-soon-overlay.ascii.md`. diff --git a/.claude/skills/plugin-marketplace-setup/SKILL.md b/.claude/skills/plugin-marketplace-setup/SKILL.md new file mode 100644 index 0000000..025456c --- /dev/null +++ b/.claude/skills/plugin-marketplace-setup/SKILL.md @@ -0,0 +1,35 @@ +--- +name: plugin-marketplace-setup +description: Install Claude Code plugin marketplaces and plugins for the farish repo. Use when adding or updating marketplaces/plugins, onboarding the repo, or when a private nsheaps marketplace fails to resolve from a sub-agent. +--- + +# Plugin & marketplace setup + +The `claude plugin` CLI authenticates through the local git proxy, so it CAN +clone and install from **private** `nsheaps/*` repos. A sub-agent's `WebFetch` +against github.com is unauthenticated and CANNOT — never use `WebFetch` to read +a private marketplace repo, and never conclude a private marketplace is +inaccessible because `WebFetch` failed. Use the CLI. + +## Steps + +1. Add a marketplace from a GitHub repo: + `claude plugin marketplace add /` + The marketplace name is the repo name (`nsheaps/agents` becomes `agents`). +2. Install a plugin at **project** scope so it is committed to + `.claude/settings.json`. Do NOT use `local` scope — that writes + `.claude/settings.local.json`, which is gitignored and not saved: + `claude plugin install @ -s project` +3. Verify with `claude plugin list` (each should show `Status: enabled`). +4. Plugins installed mid-session load fully (skills/agents/hooks/MCP servers) + only on the next session start. There is no in-session reload tool exposed to + the agent. +5. `.claude/settings.json` must declare `extraKnownMarketplaces` for every + non-default marketplace so a fresh checkout can resolve plugins. The official + `claude-plugins-official` marketplace is a default and need not be declared. + +## farish marketplaces & plugins + +- Marketplaces: `agents` (`nsheaps/agents`), `ai-mktpl` (`nsheaps/ai-mktpl`). +- The authoritative enabled-plugin list lives in `.claude/settings.json` under + `enabledPlugins`. diff --git a/.claude/skills/spec-writing-draft-outline/SKILL.md b/.claude/skills/spec-writing-draft-outline/SKILL.md new file mode 100644 index 0000000..d3a58b3 --- /dev/null +++ b/.claude/skills/spec-writing-draft-outline/SKILL.md @@ -0,0 +1,41 @@ +--- +name: spec-writing-draft-outline +description: > + Scaffold the frontmatter and bullet-note placeholders for all 13 required + sections of a page spec. Use at the start of writing a spec, before any + prose is written. Trigger phrases: "draft the outline", "scaffold the spec", + "create the spec outline". Called from the spec-writing skill step 3. +--- + +# Spec — Draft Outline + +Produces a scaffolded `SPEC.md` with frontmatter and placeholder bullets. No +prose is written yet — that is done by `spec-writing-write-section`. + +## Steps + +1. Read `docs/pages/PAGE-SPEC.md`: + - Note the 6 required frontmatter keys and their allowed values. + - Note the 13 required section headings in order. + +2. Read `docs/pages/INDEX.md` to confirm the page's slug, title, route, and + tag for this page. + +3. Open `docs/pages//SPEC.md` (create if missing). + +4. Write the YAML frontmatter block at the top of the file. All 6 keys are + required; set `status: draft`. Infer `backend_dependent` from the tag in + INDEX.md (`browser-only` / `static` → `no`; `backend` → `yes`; mixed → + `partial`). + +5. Add all 13 section headings in the exact order specified in PAGE-SPEC.md. + Use the exact heading text shown in the table (e.g. `## Route & Access`). + +6. Under each section heading, write 2–4 bullet notes that describe what + this specific page will need in that section. Draw context from: + - INDEX.md (the page's one-sentence description and tag). + - `docs/INITIAL_PROMPT.md` (feature requirements that affect this page). + - Sibling `docs/pages//SPEC.md` files for cross-page links. + Add `[citation needed]` where a footnote reference will be required. + +7. Do not write prose or fill any section beyond bullet notes. Save and stop. diff --git a/.claude/skills/spec-writing-review/SKILL.md b/.claude/skills/spec-writing-review/SKILL.md new file mode 100644 index 0000000..6bbbae9 --- /dev/null +++ b/.claude/skills/spec-writing-review/SKILL.md @@ -0,0 +1,54 @@ +--- +name: spec-writing-review +description: > + Validate a complete page spec against PAGE-SPEC.md. Checks frontmatter, + section completeness, format rules, citation coverage, and line count. + Use after all sections are written. Trigger phrases: "review the spec", + "validate the spec", "check the spec", "review SPEC.md". Called from + spec-writing skill step 5. +--- + +# Spec — Review & Validate + +Validates a finished `docs/pages//SPEC.md` against the schema in +`docs/pages/PAGE-SPEC.md`. Produces a pass/fail checklist. + +## Steps + +1. Read `docs/pages/PAGE-SPEC.md` in full — this is the checklist you + validate against. + +2. Read `docs/pages//SPEC.md` in full. + +3. **Frontmatter check.** Verify all 6 required keys are present and hold + valid values: + - `title` matches INDEX.md. + - `slug` matches the directory name. + - `route` has the correct URL pattern (`:param` for dynamic segments). + - `tag` is one of: `browser-only` | `backend` | `static` | `system`. + - `status` is one of: `draft` | `reviewed` | `live`. + - `backend_dependent` is one of: `no` | `partial` | `yes`. + +4. **Section order check.** Confirm all 13 sections appear in the exact order + from PAGE-SPEC.md. No heading may be missing; empty sections must contain + `_None._` not an empty body. + +5. **Section format checks:** + - `## States` — 4-column table with a State, Trigger, and Renders column at + minimum. Every state must name its trigger and what it renders. + - `## Data` — each datum named, sourced, and marked `local` or `remote`. + - `## Interactions` — every bullet uses `action → outcome` format. + - `## Layout` — contains a Mermaid diagram or ASCII sketch (not prose only). + - `## References` — only footnote definitions (`[^N]: ...`); no inline links. + +6. **Citation check.** For any non-obvious claim (fact, constraint, or design + decision that a builder would question), verify a `[^N]` reference exists + and is defined in `## References`. + +7. **Line count.** Count the file's lines. Flag in the report if > 250. + +8. **Produce a checklist report** listing every item above as PASS or FAIL. + For each FAIL, state the section and what is missing or malformed. + +9. If any FAILs remain, return to the `spec-writing` skill at step 6 for the + affected sections. Re-run this review after each fix. diff --git a/.claude/skills/spec-writing-write-section/SKILL.md b/.claude/skills/spec-writing-write-section/SKILL.md new file mode 100644 index 0000000..6ec9c74 --- /dev/null +++ b/.claude/skills/spec-writing-write-section/SKILL.md @@ -0,0 +1,45 @@ +--- +name: spec-writing-write-section +description: > + Fill in one section of a page spec — replace bullet-note placeholders with + polished prose, tables, or diagrams following the section's format rules. + Use after spec-writing-draft-outline, for each section in turn. Trigger + phrases: "write section X", "fill in the section", "complete section". + Called from spec-writing skill step 4. +--- + +# Spec — Write Section + +Fills one named section of a `docs/pages//SPEC.md` with finished +content. Run once per section; called 13 times total per spec. + +## Steps + +1. Identify the section to fill (e.g. `## States`) and read its bullet notes + in the current `SPEC.md`. + +2. Read the matching row in the section table in `docs/pages/PAGE-SPEC.md` + for this section's content rules. For `## States` and `## Data`, also + read the good/bad examples in that file. + +3. Write the section content using the correct format: + - `## Summary` — 1–3 sentences of plain prose. + - `## Route & Access` — URL pattern, tag value, preconditions. + - `## Users & Entry Points` — prose or bullets of who arrives and from where. + - `## Layout` — Mermaid `flowchart` or ASCII sketch of page regions. + - `## Components` — bulleted list, one component per line with a one-line role. + - `## States` — 4-column table (State | Trigger | Renders | Notes). + - `## Interactions` — `action → outcome` bullets. + - `## Data` — bulleted list; each item named, described, marked `local` or `remote`. + - `## Navigation` — in-links and out-links. + - `## Responsive` — prose or bullets on mobile/narrow adaptations. + - `## Open Questions` — bulleted list, or `_None._`. + - `## References` — markdown footnote definitions (`[^N]: ...`). + +4. Resolve all `[citation needed]` placeholders in this section. Add a + `[^N]` inline reference and define it in `## References`. + +5. Wrap prose at ~90 columns. If the section content alone would exceed + ~40 lines, note it in `## Open Questions` rather than silently trimming. + +6. Save the section. Verify the heading text matches PAGE-SPEC.md exactly. diff --git a/.claude/skills/spec-writing/SKILL.md b/.claude/skills/spec-writing/SKILL.md new file mode 100644 index 0000000..e6faced --- /dev/null +++ b/.claude/skills/spec-writing/SKILL.md @@ -0,0 +1,50 @@ +--- +name: spec-writing +description: > + Write a complete page spec (SPEC.md) for a farish site page. Use when asked + to "write the spec for X", "fill in the spec", "create SPEC.md for ", + "author the page spec", or when working through prompt step 12. Orchestrates + the draft-outline → write-section → review subskills for one page at a time. +--- + +# Spec Writing + +Process for writing a single `docs/pages//SPEC.md`. Designed so step 12 +of the initial prompt is "for each page in INDEX.md, run this skill". + +See `docs/pages/PAGE-SPEC.md` for the required shape, section rules, and +good/bad examples. This skill does not inline those rules — refer to that +document throughout. + +## Steps + +1. Identify the target page. Read `docs/pages/INDEX.md` and note the page's + slug, title, route, tag, and backend_dependent value. + +2. Verify that `docs/pages//SPEC.md` exists (it may be empty from + prompt step 8). Create the file and its parent directory if missing. + +3. Run `Skill(spec-writing-draft-outline)` to write the YAML frontmatter and + add bullet-note placeholders under each of the 13 required section headings. + +4. For each section in order (1 through 13), run + `Skill(spec-writing-write-section)` to replace the bullet notes with + polished prose, tables, or diagrams as appropriate for that section. + +5. Run `Skill(spec-writing-review)` to validate the complete spec against + `docs/pages/PAGE-SPEC.md`. + +6. For each issue the review identifies, return to step 4 for the affected + section(s), then re-run step 5. Repeat until the review passes with no + remaining issues. + +7. Commit the completed spec atomically: + `docs(pages): add page spec` + +## Notes + +- Keep the spec under ~250 lines (PAGE-SPEC.md rule). If it grows past that, + raise it in `## Open Questions` rather than silently trimming content. +- All Mermaid diagrams must render on GitHub (amendment A6 of the initial + prompt). Test locally if unsure. +- Use `[^N]` footnotes in `## References` for any non-obvious claim. diff --git a/.claude/skills/validate-app-progress/SKILL.md b/.claude/skills/validate-app-progress/SKILL.md new file mode 100644 index 0000000..32e71ad --- /dev/null +++ b/.claude/skills/validate-app-progress/SKILL.md @@ -0,0 +1,76 @@ +--- +name: validate-app-progress +description: > + Validate the farish project's standing CI/Pages requirements still hold: + CI green, CI codepaths == local tooling, screenshots of every page on main, + docs/specs published to GitHub Pages. Use after any workflow change, before + marking a step done, or when asked to "check app progress" / "verify the CI + pipeline" / "are the standing requirements met". +--- + +# Validate App Progress + +The standing requirements set by initial-prompt step 28[^step28]. These hold +for **every** task from step 28 onward — re-check them whenever a workflow, +mise task, or build script changes, and before marking any step complete. + +## Standing requirements + +1. **CI is always green.** Every workflow check on the branch must pass. A red + check means the task is not done — fix locally, re-push, re-verify. + +2. **CI codepaths == local dev tooling.** CI must run the *same* commands a + developer runs locally — `mise run check` / `mise run pages-site` / the + `e2e` package script — never a CI-only reimplementation. If they diverge, + CI is not a trustworthy gate (step 24). + +3. **Screenshots of every page, on `main`.** On every push to `main`, the + `pages.yml` workflow runs the Playwright suite to screenshot every built + page and record a video. The page manifest (`apps/web/e2e/pages.ts`) is the + source of truth for "every page" — new pages are picked up automatically. + +4. **Screenshots committed back: `[skip ci]`, date-based path.** The captured + PNGs are committed to `docs/screenshots/-/`. The commit + message contains `[skip ci]` and the commit-back runs only on push-to-main + and only when there is a diff — three guards so it cannot loop. + +5. **Screenshots published to GitHub Pages.** The same workflow run publishes + the screenshot runs to the Pages site under `/screenshots/`. + +6. **Specs/docs published to Pages on every `main` push.** `pages.yml` runs + the `pages-site` mise task to render `docs/**.md` (specs, wireframes, API + designs — Mermaid-aware, A6) into the Pages site under `/docs/`. + +## Verification steps + +1. List the workflows: `ls .github/workflows/`. Confirm `ci.yml`, + `tilt-ci.yml`, and `pages.yml` exist. + +2. Confirm CI/local parity: each workflow's build/test step invokes a + `mise run ` or a package script — not an inline reimplementation. + +3. Run the local gate: `mise run check` must be green. + +4. Exercise the Pages pipeline locally (the exact CI codepath): + - `nx run web:build` + - `cd apps/web && bun run e2e` — screenshots land in `e2e/output/screenshots/`. + - `bun run scripts/publish-screenshots.ts local-check` — copies to a + date-based path under `docs/screenshots/` (delete the test dir after). + - `mise run pages-site` — assembles `./_site` (app + docs + screenshots). + +5. Confirm `pages.yml` loop-safety: the commit-back step is gated on + push-to-main, uses a `[skip ci]` message, and no-ops on an empty diff. + +6. Confirm A5 dry-run: every workflow runs in full on `pull_request` with only + irreversible side-effects (commit-back, Pages deploy) gated off. + +7. If a workflow changed, also run `Skill(validate-change-ci)`. + +## Notes + +- This skill is the *what must always be true*; `validate-change-ci` is the + *how to check one workflow*. Run both when a workflow changes. +- Pipeline details: [`.github/workflows/pages.yml`](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/.github/workflows/pages.yml), + [`scripts/README.md`](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/scripts/README.md). + +[^step28]: [docs/INITIAL_PROMPT.md — step 28](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/docs/INITIAL_PROMPT.md#L306) diff --git a/.claude/skills/validate-change-ci/SKILL.md b/.claude/skills/validate-change-ci/SKILL.md new file mode 100644 index 0000000..6c6d709 --- /dev/null +++ b/.claude/skills/validate-change-ci/SKILL.md @@ -0,0 +1,65 @@ +--- +name: validate-change-ci +description: > + Validate a CI/GitHub Actions workflow change (.github/workflows/). Use when + asked to "validate a workflow change", "check the CI config", "review a + GitHub Actions workflow", or from validate-change when changed paths are + under .github/workflows/. +--- + +# Validate Change — CI + +Validation procedure for GitHub Actions workflow changes. + +> **Keep up to date.** Update this skill as new workflows are added and CI +> patterns solidify in steps 22–28 of the initial prompt. + +## Steps + +1. Confirm the workflow YAML is syntactically valid: + - Use `actionlint` if available, otherwise manually verify YAML structure. + - Every job must have `runs-on`, every step must have `uses` or `run`. + +2. Verify dry-run mode exists (amendment A5[^a5]): + - Every workflow **must** support a dry-run mode gated by an environment + variable (e.g. `DRY_RUN: true`). + - Confirm the dry-run path exercises the workflow's core logic without + making irreversible changes. + +3. Check action references: + - All `uses:` actions must be pinned to a commit SHA or a specific tag + (not floating `@main` or `@master`). + - Prefer off-the-shelf actions over custom ones (step 22 guidance). + +4. Verify triggers are appropriate: + - `push` / `pull_request` workflows should not fire on every branch unless + intentional. + - Scheduled workflows should have a sensible cron interval. + - Confirm the workflow does not run on `main` until the PR is merged (or + uses dry-run on PRs per A5). + +5. Confirm secrets and environment variables are accessed correctly: + - Secrets use `${{ secrets.NAME }}`, not hardcoded values. + - Any new secret references are documented in the PR description. + +6. For screenshot/artifact workflows (step 28 requirement): + - Verify the workflow uploads artifacts using `actions/upload-artifact`. + - Confirm artifact paths are deterministic and do not include timestamps + in directory names (use a date-named path per step 28). + +## Notes + +- Amendment A5 requires all workflows to support dry-run on the dev branch.[^a5] +- `CI_DRY_RUN` is computed from `github.event_name == 'pull_request'` — no repo + variable needed. PRs automatically run in dry-run mode. +- CI commit-backs (e.g. the `pages.yml` screenshot commit) must carry a + `[skip ci]` marker in the commit message so the push does not re-trigger + workflows — GitHub honours `[skip ci]` and `[no ci]` identically (step 28). +- nx cache lives in `.nx/cache/`; cache it with `actions/cache@v4` keyed on + `runner.os` + `hashFiles('bun.lock')`. +- Required status check context for branch protection: `check` (the job id in + `ci.yml`). Verify after first CI run — GitHub may report it as `CI / check`. +- **Update this skill** when step 27 adds the `tilt ci` workflow and when step 28 + finalises screenshot upload patterns. + +[^a5]: Amendment A5, `docs/INITIAL_PROMPT.md` — diff --git a/.claude/skills/validate-change-code/SKILL.md b/.claude/skills/validate-change-code/SKILL.md new file mode 100644 index 0000000..c5cc161 --- /dev/null +++ b/.claude/skills/validate-change-code/SKILL.md @@ -0,0 +1,58 @@ +--- +name: validate-change-code +description: > + Validate a source-code change (TypeScript, bun packages, monorepo apps/libs/ + services/packages). Use when asked to "validate code changes", "check the code + compiles", or from validate-change when changed paths are under apps/, + services/, lib/, or packages/. +--- + +# Validate Change — Code + +Validation procedure for TypeScript/bun source-code changes. + +> **Tooling confirmed in step 22** — bun + nx + Biome. See +> [`docs/monorepo/`](https://github.com/nsheaps/farish/tree/claude/ai-3d-model-generator-XjoUi/docs/monorepo). + +## Steps + +1. Identify which bun package(s) contain the changed files. Each package is + rooted at a `package.json` under `lib/`, `packages/`, `apps/`, or + `services/`. + +2. Run the full validation gate from the repo root — nx handles dependency + ordering and caching, so this covers the affected packages and everything + that depends on them: + ``` + mise run check + ``` + `check` runs lint → test → build. It is the exact sequence CI runs. + +3. To validate a single project and its dependencies (faster inner loop): + ``` + nx run-many --target=lint --target=test --target=build --projects= + ``` + or for one project + its dependency chain: `nx build `. + +4. The `build` target runs `tsc` (type-check **and** emit). A clean + `mise run build` is the type-check — there is no separate `typecheck` + script. + +5. Confirm all tests pass; no skipped tests should be introduced without a + reason recorded in the test file. + +6. If a package has a `release` script and the change affects public API or + versioning, run `nx run :release` (note: `nx release` is a different, + reserved command — always use `nx run :release`). + +7. Run `Skill(build-confidence-by-trying-to-prove-incorrect)` for any complex + logic change to surface edge cases before committing. + +## Notes + +- **Always go through `mise run`** — never call `nx` directly in CI; local and + CI codepaths must stay identical (initial prompt step 24). +- All four run-scripts (`lint`, `format`, `test`, `build`) are required in every + bun package; `release` is optional for publishable packages. +- Custom lint rules live in `.biome/plugins/` (GritQL). `mise run lint` loads + them. See [`docs/monorepo/lint-format.md`](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/docs/monorepo/lint-format.md). diff --git a/.claude/skills/validate-change-docs/SKILL.md b/.claude/skills/validate-change-docs/SKILL.md new file mode 100644 index 0000000..d74a77a --- /dev/null +++ b/.claude/skills/validate-change-docs/SKILL.md @@ -0,0 +1,53 @@ +--- +name: validate-change-docs +description: > + Validate a documentation change (docs/ directory, root README.md, or other + markdown files that are not page/API specs). Use when asked to "validate doc + changes", "check the docs", or from validate-change when changed paths are + under docs/ (non-spec) or root *.md files. +--- + +# Validate Change — Docs + +Validation procedure for documentation changes. + +> **Keep up to date.** Update this skill as markdown tooling is confirmed in +> steps 22+ of the initial prompt. + +## Steps + +1. Check all internal links resolve: + - Relative links (`[text](../other.md)`) must point to files that exist. + - Anchor links (`#section-heading`) must match an actual heading in the + target file. + - Tip: `find . -name "*.md" | xargs grep -l "\[" | head` to locate files + with links, then verify manually or with a link-checker tool. + +2. Verify Mermaid diagrams render correctly (amendment A6[^a6]): + - Every `\`\`\`mermaid` block must be syntactically valid. + - Test locally with the Mermaid CLI (`mmdc`) or by previewing on GitHub + if available. + - Diagrams are required for concepts involving architecture, data flows, + state machines, or sequences. + +3. Run markdown lint (tooling TBD — update when step 22 lands): + ``` + bun run lint # or mise run lint once configured + ``` + +4. Verify footnote references are consistent: + - Every `[^N]` inline citation must have a corresponding `[^N]: url` + definition at the bottom of the file. + - No orphan definitions (definitions without a matching inline citation). + +5. For diagrams or visuals, confirm they are generated by automation (not + manually placed screenshots) per the UI screenshot evidence rule. + +## Notes + +- Do NOT run this skill for `docs/pages/**/SPEC.md` or `docs/api/**/SPEC.md` + changes — use `validate-change-spec` for those. +- Amendment A6 requires Mermaid diagrams for all conceptual documentation.[^a6] +- **Update this skill** when step 22 confirms the markdown lint tool. + +[^a6]: Amendment A6, `docs/INITIAL_PROMPT.md` — diff --git a/.claude/skills/validate-change-infra/SKILL.md b/.claude/skills/validate-change-infra/SKILL.md new file mode 100644 index 0000000..8dac0e5 --- /dev/null +++ b/.claude/skills/validate-change-infra/SKILL.md @@ -0,0 +1,54 @@ +--- +name: validate-change-infra +description: > + Validate an infrastructure change (infra/ directory, .github/settings.yml, + renovate.json, Vercel config). Use when asked to "validate infra changes", + "check the infra config", or from validate-change when changed paths are under + infra/, .github/settings.yml, or renovate.json. +--- + +# Validate Change — Infra + +Validation procedure for infrastructure-as-code changes. + +> **Keep up to date.** Infra tooling is decided in step 26 of the initial +> prompt (Vercel for backend, `repository-settings` GitHub App for repo config, +> Renovate for dependency updates). Update this skill as tooling is confirmed. + +## Steps + +1. Identify which infra file(s) changed: + - `infra/` — IaC definitions (tooling TBD at step 26) + - `.github/settings.yml` — repo settings via `repository-settings` app[^repo-settings] + - `renovate.json` / `renovate.json5` — Renovate dependency config[^renovate] + +2. For `.github/settings.yml`: + - Verify YAML syntax is valid. + - Confirm branch protection rules include the `claude/ai-3d-model-generator-XjoUi` + branch as appropriate (amendment A5). + - Check no required status checks reference non-existent workflows. + +3. For `renovate.json` / `renovate.json5`: + - Confirm it extends `github>nsheaps/renovate-config`[^nsheaps-renovate] + (amendment A2). + - Validate JSON/JSON5 syntax. + +4. For `infra/` changes (IaC): + - Run the plan/validate command for the chosen tool (TBD at step 26). + - Confirm no secrets or credentials are hardcoded. + - Verify any environment variables referenced exist in the deployment target. + +5. If changes affect Vercel deployment (amendment A3): + - Confirm free-plan constraints are respected (no paid features). + - Verify environment variable names match what the app expects. + +## Notes + +- Amendment A1 mandates `repository-settings` app for all repo config.[^repo-settings] +- Amendment A3 allows Vercel on the free plan only.[^vercel] +- **Update this skill** when step 26 finalises the IaC tooling choice. + +[^repo-settings]: Repository Settings App — +[^renovate]: Renovate documentation — +[^nsheaps-renovate]: nsheaps/renovate-config — +[^vercel]: Vercel free plan — diff --git a/.claude/skills/validate-change-skill/SKILL.md b/.claude/skills/validate-change-skill/SKILL.md new file mode 100644 index 0000000..3c98e70 --- /dev/null +++ b/.claude/skills/validate-change-skill/SKILL.md @@ -0,0 +1,54 @@ +--- +name: validate-change-skill +description: > + Validate a skill, rule, hook, or agent config change (.claude/skills/, + .claude/rules/, .claude/hooks/, .claude/settings.json). Use when asked to + "validate a skill change", "check the rule I added", "verify the hook", or + from validate-change when changed paths are under .claude/. +--- + +# Validate Change — Skill / Config + +Validation procedure for Claude skill, rule, hook, and agent config changes. + +## Steps + +1. For skill files (`SKILL.md`): + - Confirm YAML frontmatter is present with both `name` and `description`. + - Verify `description` contains trigger phrases (natural language that would + cause an agent to invoke this skill). + - Count paragraphs — if more than 5, the skill needs to be split.[^keep-small] + - Confirm the skill is a numbered instruction list, not free-form prose. + - If the skill references other skills, confirm those skills exist at + `.claude/skills//SKILL.md`. + +2. For rule files (`.claude/rules/*.md`): + - Confirm the rule is genuinely something that must be visible on every API + call (rules consume context budget).[^rules-context] + - If it describes "how to do something", move it to a skill instead. + - Keep the rule concise. + +3. For hook scripts: + - Confirm the script has a shebang line and is executable (`chmod +x`). + - Verify the script does not suppress errors with `2>/dev/null` for + significant operations. + - Test the hook by running it with representative input. + +4. For `.claude/settings.json`: + - Run `Skill(update-config)` for any settings changes. + - Confirm no sensitive values (secrets, API keys) are stored in the file. + - Verify hooks reference scripts by absolute path or path relative to the + repo root. + +5. Commit immediately — per the always-keep-improving rule, config/skill changes + must be committed right after making them.[^commit-rule] + +## Notes + +- Skills are validated by trying them. If possible, invoke the new/changed skill + and confirm it produces the expected output. +- **Update this skill** if new config file types are introduced. + +[^keep-small]: KEEP SKILLS SMALL rule — `docs/INITIAL_PROMPT.md` step 4, always-keep-improving.md. +[^rules-context]: Rules take up context — prefer skills and hooks per priority order in always-keep-improving.md. +[^commit-rule]: Commit immediately rule — `.claude/rules/always-keep-improving.md`. diff --git a/.claude/skills/validate-change-spec/SKILL.md b/.claude/skills/validate-change-spec/SKILL.md new file mode 100644 index 0000000..25c0da8 --- /dev/null +++ b/.claude/skills/validate-change-spec/SKILL.md @@ -0,0 +1,49 @@ +--- +name: validate-change-spec +description: > + Validate a page or API spec change (docs/pages/**/SPEC.md or + docs/api/**/SPEC.md). Use when asked to "validate the spec", "check the spec + I wrote", "review a spec change", or from validate-change when changed paths + match docs/pages/**/SPEC.md or docs/api/**/SPEC.md. +--- + +# Validate Change — Spec + +Validation procedure for page and API spec file changes. + +## Steps + +### For page specs (`docs/pages//SPEC.md`) + +1. Run `Skill(spec-writing-review)` — it validates the spec against + `docs/pages/PAGE-SPEC.md` and checks all 13 required sections. + +2. Confirm the spec is under ~250 lines (PAGE-SPEC.md rule). If longer, raise + the issue in `## Open Questions` rather than silently trimming. + +3. Verify every non-obvious claim has a `[^N]` footnote with a URL.[^footnotes] + +4. Check that any Mermaid diagrams in the spec render correctly (amendment A6): + - No diagram should reference undefined node IDs. + - Test locally or via GitHub preview. + +5. Confirm the spec's `backend_dependent` flag in the frontmatter is accurate — + features requiring a backend get a "coming soon" overlay per step 26. + +### For API specs (`docs/api//SPEC.md`) + +6. Run `Skill(page-to-api-requirements)` review steps to confirm the endpoint + matches what the page specs require. + +7. Verify the spec documents: method, path, request schema, response schema, + auth requirements, error codes, and WebSocket vs. REST decision. + +8. Check that the endpoint slug matches the file path exactly. + +## Notes + +- Page specs and API specs have different required shapes. Confirm which type + you are validating before running the steps above. +- **Update this skill** as the spec formats evolve through steps 17–20 iteration. + +[^footnotes]: Footnote requirement from `docs/INITIAL_PROMPT.md` step 4 (always-keep-improving.md). diff --git a/.claude/skills/validate-change/SKILL.md b/.claude/skills/validate-change/SKILL.md new file mode 100644 index 0000000..7fe9b91 --- /dev/null +++ b/.claude/skills/validate-change/SKILL.md @@ -0,0 +1,47 @@ +--- +name: validate-change +description: > + Validate a change just made to the farish repo. Use when asked to "validate + this change", "check my change", "run validation", "verify what I just did", + or after any commit before pushing. Identifies the change type and routes to + the matching type-specific validation subskill. +--- + +# Validate Change + +Drilldown router: identifies what kind of change was just made and runs the +right validation procedure for each type. + +## Steps + +1. Identify changed files: + ``` + git diff --name-only HEAD + ``` + Use staged-only if not yet committed: `git diff --name-only --cached` + +2. Map each changed path to one or more change types using this table: + + | Changed path matches | Type | Subskill | + |---------------------------------------------------|--------|------------------------------| + | `apps/`, `services/`, `lib/`, `packages/` | code | `validate-change-code` | + | monorepo root config (`mise.toml`, `.mise/`, `nx.json`, `biome.json`, `.biome/`, `package.json`, `tsconfig*.json`, `.envrc`) | code | `validate-change-code` (run `mise run check`) | + | `infra/`, `.github/settings.yml`, `renovate.json` | infra | `validate-change-infra` | + | `.github/workflows/` | ci | `validate-change-ci` | + | `docs/` (non-spec), root `*.md` | docs | `validate-change-docs` | + | `.claude/skills/`, `.claude/rules/`, `.claude/hooks/` | skill | `validate-change-skill` | + | `docs/pages/**/SPEC.md`, `docs/api/**/SPEC.md` | spec | `validate-change-spec` | + +3. For each matched type, run the corresponding subskill in order. + Multiple types may match for a single change — run all of them. + +4. If any subskill reports a failure, fix it before pushing. + +## Notes + +- This skill and its subskills are intentionally lean — tooling is added in + steps 22+. **Update the relevant subskill whenever new tooling is confirmed.** +- When unsure of the type, err toward running more subskills rather than fewer. +- Source: step 21 of `docs/INITIAL_PROMPT.md`[^prompt-21]. + +[^prompt-21]: [docs/INITIAL_PROMPT.md — step 21](https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/docs/INITIAL_PROMPT.md#L245) diff --git a/.claude/tasks/1.yaml b/.claude/tasks/1.yaml new file mode 100644 index 0000000..15042eb --- /dev/null +++ b/.claude/tasks/1.yaml @@ -0,0 +1,36 @@ +id: "1" +subject: Install + configure 1pass and github-app plugins in farish for GitHub auth +status: in_progress +activeForm: Installing 1pass + github-app plugins for GitHub auth +description: |- + Add 1pass@ai-mktpl-local to farish .claude/settings.json enabledPlugins + (github-app@ai-mktpl-local is already enabled). Install the op CLI, wire the + OP_SERVICE_ACCOUNT_TOKEN, and use it to obtain GitHub credentials so gh / API + access works without the user authorizing as themselves. + + Hand-written YAML task (gate-escape): the task-utils MCP server is not loaded + this session (on-device binary build never completed), so task_create is + unavailable. This file seeds the v0.1.4 flat YAML store directly. + + + - [x] 1pass@ai-mktpl-local added to farish enabledPlugins + RESULT(2026-05-21T19:00Z): added in commit 48f91cd. + - [x] op CLI installed and resolvable + RESULT(2026-05-21T19:00Z): op 2.34.0 at /home/user/bin/.local/op. + - [x] OP_SERVICE_ACCOUNT_TOKEN verified working + RESULT(2026-05-21T19:00Z): op whoami -> SERVICE_ACCOUNT; vault -> AI-Jack. + - [x] GitHub credentials obtained; an authenticated API call succeeds + RESULT(2026-05-21T19:00Z): minted jack-nsheaps[bot] installation token from + op://AI-Jack/github--app--jack; gh api repos/nsheaps/farish succeeded. + - [x] settings.json change committed + pushed + RESULT(2026-05-21T19:00Z): committed 48f91cd, pushed to the PR branch. + + + Kept in_progress so the write-gate stays satisfied across the restart needed + to load the task-utils MCP server and apply the 1pass/github-app config. +blocks: [] +blockedBy: [] +metadata: {} +source: task-utils-mcp +createdAt: "2026-05-21T18:30:00Z" +updatedAt: "2026-05-21T18:30:00Z" diff --git a/.claude/tasks/2.yaml b/.claude/tasks/2.yaml new file mode 100644 index 0000000..978448b --- /dev/null +++ b/.claude/tasks/2.yaml @@ -0,0 +1,34 @@ +id: "2" +subject: Add break-glass bypass to the task-utils write-gate hook +status: pending +activeForm: Adding break-glass bypass to the write-gate +description: |- + The write-gate (require-task-in-progress.sh) can deadlock: when no task + system is reachable (MCP server down, built-in Task tools absent), the gate + is unsatisfiable and blocks all Write/Edit with no escape. + + Add a break-glass mechanism: + - Hash the PreToolUse input (tool_name + tool_input). + - Persist the last-denied hash (sidecar state file). + - If the same call arrives twice in a row, ALLOW the second one through. + - Record the bypass (count + timestamp) in the sidecar log. + - Print a big, loud warning to the agent on the bypassed call. + + Apply to require-task-in-progress.sh; consider task-invariant.sh too. + Part of the task-utils improvements (initial directive items 5-6). + + + - [ ] Hash computed from tool_name + tool_input + - [ ] Identical consecutive call allowed on 2nd attempt + - [ ] Bypass logged with count + timestamp + - [ ] Loud warning emitted to the agent on bypass + - [ ] Non-repeated denials still blocked normally + - [ ] Tests cover deny / bypass / reset-on-different-call + +blocks: [] +blockedBy: [] +metadata: + source-message: handler Discord 2026-05-21 — break-glass gate idea +source: task-utils-mcp +createdAt: "2026-05-21T18:50:00Z" +updatedAt: "2026-05-21T18:50:00Z" diff --git a/.claude/tasks/farish-build.yaml b/.claude/tasks/farish-build.yaml new file mode 100644 index 0000000..ba50c89 --- /dev/null +++ b/.claude/tasks/farish-build.yaml @@ -0,0 +1,34 @@ +id: farish-build +subject: Build the farish AI 3D-model generator - drive prompt steps 1-37 (nsheaps/farish#1) +activeForm: Building the farish AI 3D-model generator (nsheaps/farish#1) +status: in_progress +description: 'Orchestration task tracking the whole farish build per docs/INITIAL_PROMPT.md. + + + ## Progress + + - Steps 1-4: Claude Code config, marketplaces, plugins, web-onboarding hook, rules + - done. + + - Steps 5-10: page index (docs/pages/INDEX.md, 11 pages), empty per-page SPEC.md + scaffolds, PAGE-SPEC.md meta-spec - done. + + - Step 11: spec-writing skill + 3 subskills - done. + + - Step 12: writing all 11 page specs with the spec-writing skill - in progress (see + step-12-page-specs.json). + + - Side: task-utils MCP task server built + CI fixed on nsheaps/agents PR #157; local-checkout + marketplaces configured. + + + ## Next + + - Step 13: review page specs as a whole. Steps 14-20: wireframing + API specs. Step + 21+: code. + + + ## 2026-05-21T05:04:53Z - record created in farish/.claude/tasks/ (committed flat + store) per handler direction; prior records were wrongly in legacy ~/.claude/tasks//.' +createdAt: '2026-05-21T05:04:53Z' +updatedAt: '2026-05-21T05:04:53Z' diff --git a/.claude/tasks/legacy/2943e6c3-e833-4fe8-9b57-a2dfa13d4ca2/1.yaml b/.claude/tasks/legacy/2943e6c3-e833-4fe8-9b57-a2dfa13d4ca2/1.yaml new file mode 100644 index 0000000..43eabd4 --- /dev/null +++ b/.claude/tasks/legacy/2943e6c3-e833-4fe8-9b57-a2dfa13d4ca2/1.yaml @@ -0,0 +1,10 @@ +id: "1" +subject: "#1: Resume farish kickoff — amend INITIAL_PROMPT, fix task-utils write-gate (nsheaps/farish#1)" +activeForm: "#1: Resuming farish kickoff — amending INITIAL_PROMPT, fixing task-utils write-gate (nsheaps/farish#1)" +status: in_progress +description: |- + Manually-authored task record. The TaskCreate/TaskUpdate tools are disabled in Claude Code on the web, so this JSON is written by hand to satisfy task-utils require-task-in-progress.sh while the upstream plugin fix is in flight. + + ## 2026-05-21T02:45:18Z — created: unblock write tools, amend docs/INITIAL_PROMPT.md (A1-A5), then fix task-utils upstream on a branch of nsheaps/agents. +createdAt: "2026-05-21T02:45:18Z" +updatedAt: "2026-05-21T02:45:18Z" diff --git a/.claude/tasks/legacy/c38d0e74-181d-496f-91b6-687ef69d3450/1.yaml b/.claude/tasks/legacy/c38d0e74-181d-496f-91b6-687ef69d3450/1.yaml new file mode 100644 index 0000000..d1f3351 --- /dev/null +++ b/.claude/tasks/legacy/c38d0e74-181d-496f-91b6-687ef69d3450/1.yaml @@ -0,0 +1,10 @@ +id: "1" +subject: "#1: farish kickoff steps 5-7 — draft & refine docs/pages/INDEX.md (nsheaps/farish#1)" +activeForm: "#1: Drafting & refining docs/pages/INDEX.md (nsheaps/farish#1)" +status: in_progress +description: |- + Manually-authored task record (TaskCreate/TaskUpdate disabled in Claude Code on the web; satisfies task-utils v0.1.0 require-task-in-progress.sh until nsheaps/agents#157 merges and v0.1.1 honors TASK_UTILS_REQUIRE_TASK=0). + + ## 2026-05-21T03:06:55Z — resumed: proceeding to prompt step 5 (compile site page list into docs/pages/INDEX.md). +createdAt: "2026-05-21T03:06:55Z" +updatedAt: "2026-05-21T03:06:55Z" diff --git a/.claude/tasks/legacy/d9a421da-f690-4f53-b939-a4bf63e025ec/2.yaml b/.claude/tasks/legacy/d9a421da-f690-4f53-b939-a4bf63e025ec/2.yaml new file mode 100644 index 0000000..75a46f9 --- /dev/null +++ b/.claude/tasks/legacy/d9a421da-f690-4f53-b939-a4bf63e025ec/2.yaml @@ -0,0 +1,10 @@ +id: "2" +subject: "#2: task-utils — MCP task-management tools replacing built-in Task tools (nsheaps/agents#157)" +activeForm: "#2: Building task-utils MCP task-management tools (nsheaps/agents#157)" +status: in_progress +description: |- + Manually-authored task record (built-in Task tools unavailable in Claude Code web). + + ## 2026-05-21T03:43:10Z — session 2026-05-21T03:43:10Z: new user request — design+build an MCP server in task-utils providing TaskCreate/Update/List/Get-equivalent tools that satisfy the task-utils hooks. Phase 1: dispatch claude-code-guide to research + plan, stop before implementing. +createdAt: "2026-05-21T03:43:10Z" +updatedAt: "2026-05-21T03:43:10Z" diff --git a/.claude/tasks/legacy/daec9fed-60d8-400d-885a-f572e1ca6e17/1.yaml b/.claude/tasks/legacy/daec9fed-60d8-400d-885a-f572e1ca6e17/1.yaml new file mode 100644 index 0000000..3db8019 --- /dev/null +++ b/.claude/tasks/legacy/daec9fed-60d8-400d-885a-f572e1ca6e17/1.yaml @@ -0,0 +1,10 @@ +id: "1" +subject: "#1: farish kickoff steps 5-7 — draft & refine docs/pages/INDEX.md (nsheaps/farish#1)" +activeForm: "#1: Drafting & refining docs/pages/INDEX.md (nsheaps/farish#1)" +status: in_progress +description: |- + Manually-authored task record (TaskCreate/TaskUpdate disabled in Claude Code on the web; satisfies task-utils require-task-in-progress.sh — the hook stays active even though `claude plugin list` reports task-utils disabled). + + ## 2026-05-21T03:18:01Z — session daec9fed: writing docs/pages/INDEX.md (prompt step 5). +createdAt: "2026-05-21T03:18:01Z" +updatedAt: "2026-05-21T03:18:01Z" diff --git a/.claude/tasks/step-12-page-specs.yaml b/.claude/tasks/step-12-page-specs.yaml new file mode 100644 index 0000000..3901fea --- /dev/null +++ b/.claude/tasks/step-12-page-specs.yaml @@ -0,0 +1,6 @@ +id: step-12-page-specs +subject: '#step-12: Write page specs for all 11 farish pages' +status: in_progress +description: 'Write docs/pages//SPEC.md for all 11 pages: home, explore, leaderboards, + generate, model-detail, my-library, profile, settings, about, coming-soon, not-found. + Following spec-writing skill process and PAGE-SPEC.md schema.' diff --git a/.claude/tasks/step-13-spec-review.yaml b/.claude/tasks/step-13-spec-review.yaml new file mode 100644 index 0000000..6258d1f --- /dev/null +++ b/.claude/tasks/step-13-spec-review.yaml @@ -0,0 +1,6 @@ +id: step-13-spec-review +subject: 'step-13: Cross-spec consistency review and adjustments' +status: in_progress +description: Review all 11 page specs as a whole for navigation consistency, shared + concept naming, PAGE-SPEC.md conformance, and completeness. Make adjustments in + place. diff --git a/.claude/tasks/step-14-wireframing-skill.yaml b/.claude/tasks/step-14-wireframing-skill.yaml new file mode 100644 index 0000000..94fd73b --- /dev/null +++ b/.claude/tasks/step-14-wireframing-skill.yaml @@ -0,0 +1,6 @@ +id: step-14-wireframing-skill +subject: '[done] step-14: Create page-wireframing skill and design system research' +status: done +description: Design a page-wireframing skill that keeps wireframes consistent for + each page. Research and recommend a Vue design system. Created docs/research/design-system-choice.md + (recommends Vuetify 3) and .claude/skills/page-wireframing/ skill family (4 skills). diff --git a/.claude/tasks/step-21-validate-change.yaml b/.claude/tasks/step-21-validate-change.yaml new file mode 100644 index 0000000..59d8002 --- /dev/null +++ b/.claude/tasks/step-21-validate-change.yaml @@ -0,0 +1,6 @@ +id: step-21-validate-change +subject: '#step-21: Create validate-change drilldown skill family' +status: in_progress +description: 'Create .claude/skills/validate-change/SKILL.md drilldown + subskills: + validate-change-code, validate-change-infra, validate-change-ci, validate-change-docs, + validate-change-skill, validate-change-spec.' diff --git a/.claude/tasks/step-22-23-monorepo.yaml b/.claude/tasks/step-22-23-monorepo.yaml new file mode 100644 index 0000000..93666cb --- /dev/null +++ b/.claude/tasks/step-22-23-monorepo.yaml @@ -0,0 +1,9 @@ +id: step-22-23-monorepo +subject: '#step-22-23: Scaffold bun/ts/nx/mise/direnv monorepo + folder structure' +status: completed +description: 'Steps 22-23 of the farish prompt. Step 22: monorepo framework (bun workspaces, + root strict tsconfig, nx task runner with dependsOn+caching, mise tool manager, + direnv .envrc, shared Biome lint+format with custom rules, docs under docs/monorepo/, + standard run-scripts per package). Step 23: package-type folder structure (services/ + apps/ plugins/ lib/ packages/ infra/ .github/ .mise/) with dummy example packages + exercising the nx task graph. Verify nx/mise/direnv locally.' diff --git a/.claude/tasks/step-26-app-framework.yaml b/.claude/tasks/step-26-app-framework.yaml new file mode 100644 index 0000000..5bd44f5 --- /dev/null +++ b/.claude/tasks/step-26-app-framework.yaml @@ -0,0 +1,12 @@ +id: step-26-app-framework +subject: 'step 26: build the app framework skeleton (API server, Vue app, Tilt, coming-soon)' +status: completed +description: 'Built the FRAMEWORK + one working example for step 26. Delivered: @farish/api + microservice API server (router, /health, GET /models example stub); @farish/web + Vue 3 + Vuetify browser app (Vite dev + static GitHub Pages build); reusable ComingSoon + mechanism with @farish/mock-data lorem/placeholder generators (worked example: ExploreComingSoonView); + @farish/api-contract shared-types lib; Tiltfile orchestrating api + web as native + processes (no containers); ghcr.io publishing target (Dockerfile + gated release + script). 46 automated tests pass; mise run check green across 9 nx projects. HomeView + calls the API end-to-end. NOT built: the 11 actual pages (steps 30-32), the tilt + ci workflow (step 27).' diff --git a/.claude/tasks/step-28-standing-ci.yaml b/.claude/tasks/step-28-standing-ci.yaml new file mode 100644 index 0000000..5d7de5c --- /dev/null +++ b/.claude/tasks/step-28-standing-ci.yaml @@ -0,0 +1,14 @@ +id: step-28-standing-ci +subject: '[done] step-28: standing CI machinery — screenshot pipeline + docs publishing + + validate-app-progress skill (nsheaps/farish#1)' +activeForm: Implementing step-28 standing CI machinery (nsheaps/farish#1) +status: completed +description: 'Step 28 of docs/INITIAL_PROMPT.md. Deliverables: (1) .github/workflows/pages.yml + — on push to main + PR dry-run (A5): build Vue app, run Playwright page tests that + screenshot every route + record videos, commit screenshots back under date-based + path with [skip ci], assemble + deploy a GitHub Pages site (app + docs + screenshot + runs). (2) Lean docs site renderer (marked-based bun script) publishing docs/ to + Pages. (3) .claude/skills/validate-app-progress/SKILL.md calling out all standing + CI requirements; update do-some-work to reference it. (4) Playwright e2e harness + in apps/web/e2e with a page manifest matching page specs. Off-the-shelf actions + only, pinned. Dry-run validatable on PR #1.' diff --git a/.claude/tasks/step-31-wireframe-match.yaml b/.claude/tasks/step-31-wireframe-match.yaml new file mode 100644 index 0000000..ead06ad --- /dev/null +++ b/.claude/tasks/step-31-wireframe-match.yaml @@ -0,0 +1,5 @@ +id: step-31-wireframe-match +subject: '#step-31: Update all 11 pages to match wireframes' +status: in_progress +description: 'Step 31 of the farish project: update each of the 11 page view components + in apps/web/src/views/ to match their wireframes. Build with Vuetify 3.' diff --git a/.claude/tasks/step16-validation.yaml b/.claude/tasks/step16-validation.yaml new file mode 100644 index 0000000..a506728 --- /dev/null +++ b/.claude/tasks/step16-validation.yaml @@ -0,0 +1,5 @@ +id: step16-validation +subject: '#step16: Cross-page validation and improvement iteration' +status: in_progress +description: Review all 11 pages and their relations, fix spec/wireframe divergences, + propagate learnings diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..eadafa6 --- /dev/null +++ b/.envrc @@ -0,0 +1,30 @@ +# .envrc — direnv shell initialization for the farish monorepo. +# +# When you `cd` into this repo, direnv loads this file and: +# 1. Activates mise, putting the pinned bun/node/direnv versions on PATH. +# 2. Adds node_modules/.bin so workspace tools (nx, biome, tsc) are callable +# directly without a `bunx`/`npx` prefix. +# +# First-time setup (once per machine): +# - install direnv : `mise use -g direnv` (or your OS package manager) +# - hook into shell : add `eval "$(direnv hook bash)"` to ~/.bashrc +# (zsh: `eval "$(direnv hook zsh)"`) +# - approve this file: run `direnv allow` from the repo root +# +# Docs: docs/monorepo/direnv.md + +# Activate mise. `use mise` is provided by mise's direnv integration; if your +# direnv does not have it, fall back to the eval form below. +if has mise; then + eval "$(mise activate bash)" + eval "$(mise hook-env -s bash)" +else + echo "direnv: mise not found on PATH — install it first (see docs/monorepo/mise.md)" >&2 +fi + +# Workspace-local binaries (nx, biome, tsc) — callable without bunx/npx. +PATH_add node_modules/.bin + +# Watch the toolchain files so direnv reloads when versions change. +watch_file mise.toml +watch_file bun.lock diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..1c1dd0c --- /dev/null +++ b/.github/README.md @@ -0,0 +1,20 @@ +# `.github/` + +GitHub integration for the farish repo. + +## Present now + +- `pull_request_template.md` — the PR template (reviewed/updated on every PR). +- `workflows/ci.yml` — monorepo lint + test + build CI gate (step 24). +- `settings.yml` — repo config as code via repository-settings app (amendment A1). + +## Coming in later prompt steps + +| File / dir | Purpose | Prompt step | +| ----------------------- | ---------------------------------------------- | ------------ | +| `workflows/deploy.yml` | Deploy the app + docs to GitHub Pages | step 27, 28 | +| `workflows/tilt-ci.yml` | End-to-end dev-workflow test with `tilt ci` | step 27 | +| `renovate.json` | Dependency automation | amendment A2 | + +CI workflows MUST invoke the **same** `mise run` entrypoints used locally, so +the local and CI codepaths never diverge (initial prompt step 24). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..9a495ca --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,32 @@ + + +## What + + + +## Prompt step + + + +## Changes + + + +- + +## Validation + + + +- [ ] `mise run check` passes locally (lint + test + build) +- [ ] `Skill(validate-change)` run for the affected change types +- [ ] Docs updated (if behaviour or tooling changed) +- [ ] CI green (or noted why not — see amendment A5 dry-run gating) + +## Notes for the reviewer + + diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..3f42ba1 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,59 @@ +# .github/settings.yml — repository configuration as code. +# +# Managed by the repository-settings GitHub App[^repo-settings]. +# Changes merged to main are automatically synced to GitHub. +# Affects amendment A1 (repo config as code) and A5 (CI dry-run gating). +# +# [^repo-settings]: https://github.com/repository-settings/app +# +# Amendment A5 — dry-run mode: +# The CI workflow computes CI_DRY_RUN automatically from the GitHub event +# type (pull_request → true, push to main → false). No repository variable +# is required; the value is derived from the event context in ci.yml. +# The branch protection below enforces that the `check` job must pass on +# the PR before merging, which is how A5's "dry-run validates before main" +# contract is enforced. + +repository: + # Basic repo metadata + name: farish + description: >- + Browser-based AI 3D-model generator — named after William Farish, who + formalised isometric projection in 1822. + homepage: "" + private: false + has_issues: true + has_projects: false + has_wiki: false + + # Merge strategy: squash-only keeps a linear history. + allow_squash_merge: true + allow_merge_commit: false + allow_rebase_merge: false + squash_merge_commit_title: PR_TITLE + squash_merge_commit_message: PR_BODY + + # Hygiene + delete_branch_on_merge: true + allow_auto_merge: false + +# Branch protection for main. +# +# required_status_checks.contexts must match the GitHub Actions check name +# exactly. For a workflow named "CI" with job id "check", GitHub reports the +# check as "check". Verify after the first CI run and update if needed. +branches: + - name: main + protection: + required_status_checks: + # Require the branch to be up-to-date before merging. + strict: true + # The check name matches the job `name: check` in ci.yml. + # If GitHub reports it as "CI / check", update this value. + contexts: + - check + enforce_admins: false + # No required reviews for now — bot-authored PRs can merge once CI green. + required_pull_request_reviews: null + # No push restrictions — any collaborator may push. + restrictions: null diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..793a730 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +# ci.yml — monorepo lint + test + build CI gate. +# +# Runs the SAME command a developer runs locally: `mise run check`. +# This satisfies initial-prompt step 24 — local and CI codepaths never diverge. +# +# Amendment A5 dry-run: on pull-request events, CI_DRY_RUN=true is set +# automatically. This lets the workflow be exercised on the dev branch +# (PR #1) before it reaches main. The dry-run only adds a notice; the +# full check still runs (lint + test + build are safe, non-destructive). +# +# Off-the-shelf actions used (per initial prompt step 22): +# - actions/checkout@v4 — standard repo checkout +# - jdx/mise-action@v2 — installs mise + pinned tools from mise.toml +# - actions/cache@v4 — caches .nx/cache for task-result reuse + +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + # Default activity types (opened, synchronize, reopened) all fire for + # draft PRs — no additional filtering needed. + # ready_for_review is included explicitly so converting a draft also + # triggers a fresh CI run. + types: [opened, synchronize, reopened, ready_for_review] + +# Automatically compute the dry-run flag from the event type (A5). +# On pull_request events: CI_DRY_RUN=true (pre-merge validation). +# On push to main: CI_DRY_RUN=false (production gate). +env: + CI_DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + +jobs: + check: + name: check + runs-on: ubuntu-latest + + steps: + # ── 1. Checkout ──────────────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v4 + with: + # fetch-depth: 0 fetches all history so nx affected (future) can + # compare against any base SHA. Has no cost for --all runs. + fetch-depth: 0 + + # ── 2. Dry-run notice (A5) ───────────────────────────────────────────── + - name: Dry-run notice + if: env.CI_DRY_RUN == 'true' + run: | + echo "::notice::CI_DRY_RUN=true — running monorepo check in dry-run mode (PR build)." + echo "All steps execute normally; no irreversible side-effects in this workflow." + + # ── 3. Install toolchain (mise) ──────────────────────────────────────── + # jdx/mise-action reads mise.toml, installs bun + node + direnv at + # their pinned versions, and adds mise's shims to PATH. + # The action's own caching is enabled (cache: true) so tool downloads + # are skipped on repeat runs. + - name: Install toolchain via mise + uses: jdx/mise-action@v2 + with: + cache: true + experimental: true + + # ── 4. Cache nx task results ─────────────────────────────────────────── + # .nx/cache is nx's content-addressed task cache. Restoring it lets nx + # skip tasks whose inputs are identical to a previous run. + # + # Key strategy: + # Exact key: OS + bun.lock hash (hits when deps haven't changed) + # Restore keys: OS only (partial hit on dep upgrades — nx + # still benefits from unchanged tasks) + - name: Cache nx task results + uses: actions/cache@v4 + with: + path: .nx/cache + key: nx-cache-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: | + nx-cache-${{ runner.os }}- + + # ── 5. Install workspace dependencies ───────────────────────────────── + # mise run bootstrap → .mise/tasks/bootstrap.sh → bun install + # --frozen-lockfile is set when CI=true (see bootstrap.sh) to prevent + # accidental lockfile modifications in CI. + - name: Bootstrap (bun install) + run: mise run bootstrap + + # ── 6. Full validation gate ──────────────────────────────────────────── + # mise run check → .mise/tasks/check.sh → nx lint + test + build (--all) + # Identical to what a developer runs locally. + - name: Check (lint + test + build) + run: mise run check diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..fe163c5 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,466 @@ +# pages.yml — screenshot pipeline + GitHub Pages deployment (initial prompt step 28). +# +# A SINGLE workflow with two sequential jobs so the screenshot commit-back and +# the Pages deployment never race over the one Pages deployment a repo gets: +# +# job `screenshots` build the Vue app → run the Playwright page suite +# (screenshot every route + record videos) → on push to +# main, commit the PNGs back under a date-based path with +# a loop-safe `[skip ci]` message. +# job `deploy` (needs: screenshots) checks out the commit-back ref, +# assembles ./_site (app + rendered docs + screenshot +# runs) via the `pages-site` mise task, and deploys it to +# GitHub Pages with the official actions/deploy-pages. +# +# Step 28 requirements satisfied: +# - screenshots of every page taken on main ........ job `screenshots` +# - committed back, date-based path, `[skip ci]` .... step "Commit screenshots" +# - published to GitHub Pages ....................... job `deploy` +# - docs/specs/wireframes published to Pages ........ job `deploy` (pages-site) +# +# Amendment A5 dry-run: on pull_request events PAGES_DRY_RUN=true. The build + +# Playwright suite + site assembly all run in full so the workflow is exercised +# on PR #1; only the two irreversible side-effects — the git commit-back and +# the Pages deployment — are gated off for dry-runs. +# +# Loop safety: the commit-back runs ONLY on push-to-main, and its message +# contains `[skip ci]` (GitHub natively skips push-triggered runs whose head +# commit message contains `[skip ci]`/`[no ci]`). The step also no-ops when +# there is no screenshot diff. Two independent guards => no infinite loop. +# +# Off-the-shelf actions only, all pinned (initial prompt step 22). Versions +# match GitHub's official Pages starter workflow (actions/starter-workflows +# pages/static.yml) — that combo is the canonical, known-good pairing: +# - actions/checkout@v4 — repo checkout +# - jdx/mise-action@v2 — installs bun + node from mise.toml +# - actions/cache@v4 — caches .nx/cache +# - actions/configure-pages@v5 — configures (+ auto-enables) Pages +# - actions/upload-pages-artifact@v3 — packages ./_site as a Pages artifact +# - actions/deploy-pages@v5 — official GitHub Pages deployment +# Playwright browsers are installed with `playwright install --with-deps` +# (the project pins @playwright/test in bun.lock — no separate setup action). + +name: Pages + +on: + push: + branches: [main] + pull_request: + branches: [main] + # Same activity types as ci.yml / tilt-ci.yml so draft PRs also trigger. + types: [opened, synchronize, reopened, ready_for_review] + +# A5 dry-run flag: true on pull_request events, false on push to main. +env: + PAGES_DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + +# Least-privilege token scopes: +# contents: write — the screenshots job commits PNGs back to the repo. +# pages: write / id-token: write — required by actions/deploy-pages. +permissions: + contents: write + pages: write + id-token: write + +# Allow only one Pages deployment at a time; never cancel an in-flight one. +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: false + +jobs: + # ── Job 1: build app, screenshot every page, commit results back ────────── + screenshots: + name: screenshots + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Dry-run notice + if: env.PAGES_DRY_RUN == 'true' + run: | + echo "::notice::PAGES_DRY_RUN=true — build + Playwright suite + site" + echo "assembly run in full; commit-back and Pages deploy are skipped." + + # Install bun + node at the versions pinned in mise.toml. + - name: Install toolchain via mise + uses: jdx/mise-action@v2 + with: + cache: true + experimental: true + + # nx content-addressed task cache — speeds up the web build. + - name: Cache nx task results + uses: actions/cache@v4 + with: + path: .nx/cache + key: nx-cache-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: | + nx-cache-${{ runner.os }}- + + - name: Bootstrap (bun install) + id: bootstrap + run: mise run bootstrap + + # node_modules/.bin holds nx (a workspace devDependency). jdx/mise-action + # exports PATH before `bun install` creates node_modules, so the raw + # `nx run web:e2e` step below cannot find nx unless this directory is + # exported to $GITHUB_PATH explicitly (same fix as tilt-ci.yml). + - name: Put node_modules/.bin on PATH + run: echo "${{ github.workspace }}/node_modules/.bin" >> "$GITHUB_PATH" + + # Cache the Playwright Chromium browser binary. + # Key: runner OS + bun.lock hash — ensures a cache miss whenever any dep + # changes (which may bump the playwright browser revision). + # Note: system deps (libnss3 etc.) are NOT cached — they are installed + # via apt by `--with-deps` on every run. The Playwright docs explicitly + # advise against caching them because the set of required packages can + # change between apt mirrors and runner OS versions. + - name: Cache Playwright browser binaries + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-browsers-${{ runner.os }}-${{ hashFiles('bun.lock') }} + + # Playwright needs its browser binaries + OS deps; the npm package alone + # is not enough. --with-deps installs the apt packages chromium needs. + # Run this step unconditionally — even on a cache hit the system deps + # must be installed (they are not cached). `playwright install` skips the + # browser download when the binary already exists in the cache; the + # --with-deps flag only re-runs the apt install portion. + - name: Install Playwright browsers (+ system deps) + id: playwright-install + run: bun run --cwd apps/web playwright install --with-deps chromium + + # Build the app, then run the screenshot suite against the production + # build. `nx run web:e2e` respects the task graph: its `dependsOn: ["^build"]` + # ensures workspace lib dist/ artefacts (@farish/api-contract, @farish/mock-data) + # are built before `vite build` runs — critical on a cold CI runner where + # no dist/ exists yet. The underlying package script is still + # `vite build && playwright test`, so local and CI run the same code. + - name: Build app + run screenshot suite + id: e2e + run: nx run web:e2e + + # Copy the raw PNGs onto a date-based path: docs/screenshots/-/. + # The script prints the run directory name; capture it for later steps. + - name: Stage screenshots on date-based path + id: stage + run: | + run_name="$(bun run scripts/publish-screenshots.ts "${{ github.run_id }}")" + echo "run_name=${run_name}" >> "$GITHUB_OUTPUT" + echo "Staged screenshot run: ${run_name}" + + # Upload the raw run (screenshots, videos, HTML report) as a workflow + # artifact — always, even on dry-runs, so PR authors can inspect output. + - name: Upload Playwright artifacts + id: upload-artifacts + uses: actions/upload-artifact@v4 + with: + name: playwright-run-${{ github.run_id }} + path: apps/web/e2e/output + if-no-files-found: warn + + # Commit the date-based screenshot directory back to the repo. + # Guard 1: push-to-main only — PR dry-runs never commit. + # Guard 2: `[skip ci]` in the message — the resulting push is ignored by + # every workflow's push trigger, so this cannot loop. + # Guard 3: no-op when `git diff` shows nothing staged. + - name: Commit screenshots ([skip ci], loop-safe) + if: env.PAGES_DRY_RUN == 'false' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/screenshots/ + if git diff --cached --quiet; then + echo "No screenshot changes to commit." + exit 0 + fi + git commit -m "chore(screenshots): publish run ${{ steps.stage.outputs.run_name }} [skip ci]" + git push origin HEAD:${{ github.ref_name }} + + # ── Failure diagnostics capture ────────────────────────────────────────── + # Runs ONLY when the job has already failed (any prior step). + # Collects every available diagnostic into ci-debug/-/ + # and commits + pushes to the PR branch with [skip ci] so it can be + # `git pull`-ed and read locally. + # + # Loop-safety analysis: + # • `if: failure()` — this step is skipped on success; a successful run + # never commits anything and never loops. + # • `[skip ci]` in the commit message — GitHub natively skips all + # push-triggered workflow runs whose head-commit message contains + # `[skip ci]`, so the diagnostics push does NOT re-trigger this job. + # • The step also exits early when `git diff --cached --quiet` (nothing + # staged), providing a third independent guard. + # • A diagnostics commit itself cannot fail (it never runs the screenshot + # suite), so a failing diagnostics step cannot re-arm this step. + - name: Capture failure diagnostics and commit back [skip ci] + if: ${{ failure() }} + run: | + set +e # keep going even if individual commands fail + + TS="$(date -u +%Y%m%dT%H%M%SZ)" + RUN_ID="${{ github.run_id }}" + DEBUG_DIR="ci-debug/${TS}-${RUN_ID}" + mkdir -p "${DEBUG_DIR}" + + echo "=== Diagnostics capture: ${DEBUG_DIR} ===" + + # ── 0. Per-step outcomes — which job step actually failed ────────── + # GitHub expands ${{ toJSON(steps) }} to a JSON object keyed by each + # step id, each with .outcome / .conclusion. This is the definitive + # answer to "which step failed". + cat > "${DEBUG_DIR}/step-outcomes.json" <<'STEP_OUTCOMES_EOF' + ${{ toJSON(steps) }} + STEP_OUTCOMES_EOF + + # ── 1. Environment / versions ────────────────────────────────────── + { + echo "=== uname -a ===" + uname -a + echo "" + echo "=== node --version ===" + node --version 2>&1 || echo "(node not found)" + echo "" + echo "=== bun --version ===" + bun --version 2>&1 || echo "(bun not found)" + echo "" + echo "=== bunx playwright --version ===" + bunx playwright --version 2>&1 || echo "(playwright not found)" + echo "" + echo "=== df -h /dev/shm ===" + df -h /dev/shm 2>&1 || echo "(df failed)" + echo "" + echo "=== df -h / ===" + df -h / 2>&1 + echo "" + echo "=== free -h ===" + free -h 2>&1 || echo "(free failed)" + echo "" + echo "=== env (filtered) ===" + env | grep -v -i 'token\|secret\|password\|key' | sort + } > "${DEBUG_DIR}/environment.txt" 2>&1 + + # ── 2. Playwright install output ─────────────────────────────────── + # Re-run playwright install and capture stdout+stderr verbatim so we + # can see whether browser binaries or system deps are missing. + bun run --cwd apps/web playwright install --with-deps chromium \ + > "${DEBUG_DIR}/playwright-install.txt" 2>&1 || true + + # ── 3. Chromium binary / launch probe ───────────────────────────── + { + echo "=== Chromium binary location ===" + find ~/.cache/ms-playwright -name 'chrome' -o -name 'chromium' 2>/dev/null | head -20 + echo "" + echo "=== Chromium executable probe ===" + CHROME_BIN="$(find ~/.cache/ms-playwright -name 'chrome' -type f 2>/dev/null | head -1)" + if [ -n "${CHROME_BIN}" ]; then + echo "Found: ${CHROME_BIN}" + "${CHROME_BIN}" --version 2>&1 || echo "(launch failed)" + "${CHROME_BIN}" --headless --disable-gpu --no-sandbox \ + --dump-dom about:blank 2>&1 | head -30 || echo "(headless launch failed)" + else + echo "No chromium binary found in ~/.cache/ms-playwright" + fi + echo "" + echo "=== ldd chromium (shared lib deps) ===" + if [ -n "${CHROME_BIN}" ]; then + ldd "${CHROME_BIN}" 2>&1 | grep -i 'not found' || echo "(no missing libs)" + fi + } > "${DEBUG_DIR}/chromium-probe.txt" 2>&1 + + # ── 4. Playwright test results ───────────────────────────────────── + if [ -d apps/web/test-results ]; then + cp -r apps/web/test-results "${DEBUG_DIR}/test-results" 2>/dev/null || true + fi + if [ -d apps/web/playwright-report ]; then + cp -r apps/web/playwright-report "${DEBUG_DIR}/playwright-report" 2>/dev/null || true + fi + # Any loose trace/stderr txt files produced by Playwright + find apps/web -name '*.txt' -newer apps/web/package.json \ + -not -path '*/node_modules/*' 2>/dev/null \ + | head -50 | while read -r f; do + cp "$f" "${DEBUG_DIR}/" 2>/dev/null || true + done + + # ── 5. Vite build / preview logs ────────────────────────────────── + # Replay the build step and capture output. + bun run --cwd apps/web build \ + > "${DEBUG_DIR}/vite-build.txt" 2>&1 || true + + # ── 6. Exact failing command repro ───────────────────────────────── + { + echo "=== Failing command ===" + echo " bun run --cwd apps/web e2e" + echo "" + echo "=== Re-running with verbose output ===" + bun run --cwd apps/web e2e 2>&1 | tail -200 + } > "${DEBUG_DIR}/e2e-repro.txt" 2>&1 || true + + # ── 7. Manifest ──────────────────────────────────────────────────── + { + echo "ci-debug collection: ${TS}-${RUN_ID}" + echo "github.ref: ${{ github.ref }}" + echo "github.sha: ${{ github.sha }}" + echo "github.run_id: ${{ github.run_id }}" + echo "github.run_number:${{ github.run_number }}" + echo "github.actor: ${{ github.actor }}" + echo "" + echo "=== Files collected ===" + find "${DEBUG_DIR}" -type f | sort + } > "${DEBUG_DIR}/MANIFEST.txt" 2>&1 + + # ── Commit and push ──────────────────────────────────────────────── + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add "${DEBUG_DIR}/" + if git diff --cached --quiet; then + echo "Nothing staged to commit (diagnostics directory is empty)." + exit 0 + fi + git commit -m "ci(debug): screenshots failure diagnostics run ${RUN_ID} [skip ci]" + + # Determine the branch to push to: on a pull_request event use + # github.head_ref (the PR branch); on a push event use github.ref_name. + PUSH_BRANCH="${{ github.head_ref || github.ref_name }}" + echo "Pushing diagnostics to branch: ${PUSH_BRANCH}" + git push origin HEAD:"${PUSH_BRANCH}" + + echo "Diagnostics committed to ${DEBUG_DIR} on branch ${PUSH_BRANCH}." + + # ── Job 2: assemble ./_site and deploy it to GitHub Pages ───────────────── + deploy: + name: deploy-pages + runs-on: ubuntu-latest + # Wait for the screenshot job so the commit-back is already on the branch + # and gets included in the published docs site. One job touches Pages. + needs: screenshots + # Deploy on push to main, OR on this PR branch which serves as the de-facto + # main while the direct-push path to main is unreachable. + if: >- + github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.head_ref == 'claude/ai-3d-model-generator-XjoUi') + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + # Check out the branch HEAD. On push events use ref_name (branch name); + # on pull_request events use head_ref (the PR source branch) so we get + # the actual app code rather than the merge-ref. + # Every step carries an `id:` so the failure-diagnostics step below can + # name the exact failing step via ${{ toJSON(steps) }} — without ids a + # step's outcome does not appear in the `steps` context. + - name: Checkout (post-commit-back) + id: checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 + + - name: Install toolchain via mise + id: mise + uses: jdx/mise-action@v2 + with: + cache: true + experimental: true + + - name: Cache nx task results + id: cache-nx + uses: actions/cache@v4 + with: + path: .nx/cache + key: nx-cache-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: | + nx-cache-${{ runner.os }}- + + - name: Bootstrap (bun install) + id: bootstrap + run: mise run bootstrap + + # node_modules/.bin holds nx; export it so the raw `nx run web:build` + # step below resolves (jdx/mise-action exports PATH before node_modules + # exists). Same fix as tilt-ci.yml and the screenshots job above. + - name: Put node_modules/.bin on PATH + id: path + run: echo "${{ github.workspace }}/node_modules/.bin" >> "$GITHUB_PATH" + + # Build the app — `pages-site` copies apps/web/dist into the site root. + - name: Build app + id: build + run: nx run web:build + + # Assemble ./_site: built app + docs/**.md rendered to HTML (Mermaid, + # A6) + every committed screenshot run. The SAME mise task a developer + # runs locally (step 24). + - name: Assemble Pages site + id: assemble + run: mise run pages-site + + - name: Configure GitHub Pages + id: configure + uses: actions/configure-pages@v5 + with: + # Auto-enable GitHub Pages (source = GitHub Actions) on the first + # deploy — the repo's Pages setting is likely not turned on yet. + enablement: true + + # Package ./_site as the Pages artifact. Pinned to @v3 to match GitHub's + # official Pages starter workflow — @v3 is the version paired with + # deploy-pages@v5 (the @v4 + deploy-pages@v4 combo previously used here + # is not the canonical pairing and was failing the deploy job). + - name: Upload Pages artifact + id: upload + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + # Official GitHub Pages deployment. @v5 matches the starter workflow. + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 + + # Failure diagnostics for the deploy job — commits step outcomes + info + # to ci-debug/ so a deploy failure is readable without GitHub API access. + - name: Capture deploy failure diagnostics [skip ci] + if: ${{ failure() }} + run: | + set +e + TS="$(date -u +%Y%m%dT%H%M%SZ)" + DEBUG_DIR="ci-debug/${TS}-${{ github.run_id }}-deploy" + mkdir -p "${DEBUG_DIR}" + cat > "${DEBUG_DIR}/step-outcomes.json" <<'STEP_OUTCOMES_EOF' + ${{ toJSON(steps) }} + STEP_OUTCOMES_EOF + { + echo "job: deploy-pages" + echo "run_id: ${{ github.run_id }}" + echo "sha: ${{ github.sha }}" + echo "event: ${{ github.event_name }}" + echo "ref: ${{ github.ref }}" + echo "head_ref:${{ github.head_ref }}" + echo "=== _site listing ===" + ls -laR _site 2>&1 | head -60 + } > "${DEBUG_DIR}/deploy-info.txt" 2>&1 + # Probe the Pages API: is Pages enabled, and how is it sourced? + # HTTP 404 => Pages not enabled (configure-pages enablement failed) + # HTTP 200 + build_type=workflow => OK (Actions-sourced) + # HTTP 200 + build_type=legacy => branch-sourced (conflicts) + # HTTP 403 => token/plan cannot manage Pages + curl -sS -o "${DEBUG_DIR}/pages-api.json" \ + -w 'HTTP %{http_code}\n' \ + -H "Authorization: Bearer ${{ github.token }}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${{ github.repository }}/pages" \ + > "${DEBUG_DIR}/pages-api-status.txt" 2>&1 + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add "${DEBUG_DIR}/" + if git diff --cached --quiet; then exit 0; fi + git commit -m "ci(debug): deploy failure diagnostics run ${{ github.run_id }} [skip ci]" + git push origin HEAD:"${{ github.head_ref || github.ref_name }}" diff --git a/.github/workflows/tilt-ci.yml b/.github/workflows/tilt-ci.yml new file mode 100644 index 0000000..2f1b4b1 --- /dev/null +++ b/.github/workflows/tilt-ci.yml @@ -0,0 +1,122 @@ +# tilt-ci.yml — end-to-end dev-workflow validation via `tilt ci`. +# +# Brings up every resource declared in the Tiltfile (deps-build, api, web) +# using native local_resource processes (no containers), waits for all to +# reach the "OK" state, then exits. Exit code 1 on any resource failure. +# +# This satisfies initial-prompt step 27 — the CI gate exercises the same +# Tilt-managed dev stack a developer runs locally (`tilt up`). +# +# Amendment A5 dry-run: on pull-request events, TILT_CI_DRY_RUN=true is set +# automatically. `tilt ci` still runs in full; the flag only adds an +# informational notice. No irreversible side effects exist in this workflow. +# +# Tilt installation: the official `tilt-dev` org does not publish a +# standalone setup-tilt action; the maintained off-the-shelf action is +# `yokawasa/action-setup-kube-tools` which installs a pinned Tilt version +# and caches it on the runner. +# +# Off-the-shelf actions used: +# - actions/checkout@v4 — standard repo checkout +# - jdx/mise-action@v2 — installs bun + node from mise.toml +# - actions/cache@v4 — caches .nx/cache +# - yokawasa/action-setup-kube-tools@v0.13.1 — installs pinned Tilt binary + +name: Tilt CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + # Same activity types as ci.yml so draft PRs also trigger. + types: [opened, synchronize, reopened, ready_for_review] + +# A5 dry-run: automatically set on pull_request events. +# On push to main: TILT_CI_DRY_RUN=false (production gate). +# On pull_request: TILT_CI_DRY_RUN=true (pre-merge validation — same full +# execution, plus an informational notice annotation). +env: + TILT_CI_DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + +jobs: + tilt-ci: + name: tilt-ci + runs-on: ubuntu-latest + + steps: + # ── 1. Checkout ──────────────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v4 + with: + # Full history so nx affected can compare against any base SHA. + fetch-depth: 0 + + # ── 2. Dry-run notice (A5) ───────────────────────────────────────────── + - name: Dry-run notice + if: env.TILT_CI_DRY_RUN == 'true' + run: | + echo "::notice::TILT_CI_DRY_RUN=true — running tilt ci in dry-run mode (PR build)." + echo "All steps execute normally; no irreversible side-effects in this workflow." + + # ── 3. Install toolchain (mise) ──────────────────────────────────────── + # mise-action installs bun + node at versions pinned in mise.toml. + # bun is needed to run the API server and the Vite dev server; + # node is needed by nx and Vite. + - name: Install toolchain via mise + uses: jdx/mise-action@v2 + with: + cache: true + experimental: true + + # ── 4. Cache nx task results ─────────────────────────────────────────── + # deps-build runs `nx run-many --target=build`; cache hits avoid + # re-compiling unchanged libs on repeat runs. + - name: Cache nx task results + uses: actions/cache@v4 + with: + path: .nx/cache + key: nx-cache-${{ runner.os }}-${{ hashFiles('bun.lock') }} + restore-keys: | + nx-cache-${{ runner.os }}- + + # ── 5. Install Tilt ──────────────────────────────────────────────────── + # tilt-dev does not publish a standalone setup-tilt action. + # yokawasa/action-setup-kube-tools is the maintained off-the-shelf + # action (https://github.com/yokawasa/action-setup-kube-tools). + # Version 0.35.2 is the same version referenced in docs/monorepo/tilt.md. + - name: Install Tilt + uses: yokawasa/action-setup-kube-tools@v0.13.1 + with: + tilt: '0.35.2' + + # ── 6. Install workspace dependencies ───────────────────────────────── + # mise run bootstrap → .mise/tasks/bootstrap.sh → bun install + # Required before tilt ci so that nx (workspace dep) and all lib + # source files are present. + - name: Bootstrap (bun install) + run: mise run bootstrap + + # ── 6a. Expose workspace binaries to PATH ───────────────────────────── + # node_modules/.bin holds nx, biome, tsc, etc. mise adds it to PATH + # within mise-managed commands (via _.path in mise.toml), but tilt ci + # spawns local_resource processes outside of mise, so those processes + # cannot find nx unless we export the directory to GITHUB_PATH. + # This step runs after bootstrap so the directory exists. + - name: Add workspace binaries to PATH + run: echo "${{ github.workspace }}/node_modules/.bin" >> $GITHUB_PATH + + # ── 7. Run tilt ci ───────────────────────────────────────────────────── + # `tilt ci` executes every resource in the Tiltfile in dependency order: + # 1. deps-build — `nx run-many --target=build` (one-shot cmd) + # 2. api — `bun run --watch services/api/src/server.ts` + # readiness probe: GET /health → 200 OK + # 3. web — `bun run --cwd apps/web dev` (Vite dev server) + # no readiness probe; OK once the process starts + # Exits 0 when all three resources reach the "OK" state, 1 on any failure. + # TILT_DISABLE_ANALYTICS=1 suppresses telemetry from CI runners. + - name: Run tilt ci + run: tilt ci + timeout-minutes: 10 + env: + TILT_DISABLE_ANALYTICS: '1' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3539f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Claude Code — local/personal settings (never committed; may hold secrets) +.claude/settings.local.json + +# Claude Code — temporary / disposable working files +.claude/tmp/ + +# Sub-agent git worktrees (local working areas, never committed) +.claude/worktrees/ + +# Claude Code — hook sidecar logs (disposable runtime logs) +.claude/logs/ + +# --- Monorepo (steps 22-23) --------------------------------------------------- + +# Dependencies +node_modules/ + +# Build output (per-package dist/, TS build info) +dist/ +*.tsbuildinfo + +# nx task-graph cache + generated graph dumps +.nx/ +.nx-graph.json + +# direnv local environment +.direnv/ + +# --- CI screenshot pipeline (step 28) ----------------------------------------- + +# Playwright e2e run artifacts (videos, traces, HTML report, raw screenshots). +# The CI workflow copies these onto a date-based path under docs/screenshots/ +# and commits THAT — the raw output dir itself is never committed. +apps/web/e2e/output/ + +# Playwright's installed browser binaries. +**/playwright-report/ + +# Assembled GitHub Pages site (app + docs + screenshots) — built by CI. +_site/ + diff --git a/.mise/tasks/README.md b/.mise/tasks/README.md new file mode 100644 index 0000000..145dd6a --- /dev/null +++ b/.mise/tasks/README.md @@ -0,0 +1,15 @@ +# `.mise/tasks/` + +Multi-line task scripts invoked by `mise run `. + +Per the farish working rules, anything beyond a one-line command that an +org-wide task needs is captured here as a script rather than inlined into +`mise.toml`. One-line tasks (`lint`, `format`, `test`, `build`, `graph`) stay +inline in `mise.toml`; multi-step tasks (`bootstrap`, `check`) live here. + +| Script | mise task | Purpose | +| -------------- | ---------------- | ---------------------------------------- | +| `bootstrap.sh` | `mise run bootstrap` | Install workspace dependencies. | +| `check.sh` | `mise run check` | Full validation gate (lint+test+build). | + +See [`docs/monorepo/mise.md`](../../docs/monorepo/mise.md) for the full guide. diff --git a/.mise/tasks/bootstrap.sh b/.mise/tasks/bootstrap.sh new file mode 100755 index 0000000..a2c378e --- /dev/null +++ b/.mise/tasks/bootstrap.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +#MISE description = "Install workspace dependencies (bun install)." +# bootstrap.sh — install all workspace dependencies. +# +# Invoked by `mise run bootstrap`. Kept as a script (not a one-line mise task) +# because it is expected to grow (e.g. git-hook install, env validation). +# +# Docs: docs/monorepo/mise.md +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +echo "==> Installing workspace dependencies with bun" +# In CI (CI=true is set by GitHub Actions and most CI providers) use +# --frozen-lockfile so bun errors rather than silently modifying bun.lock. +if [ "${CI:-}" = "true" ]; then + bun install --frozen-lockfile +else + bun install +fi + +echo "==> Bootstrap complete" diff --git a/.mise/tasks/check.sh b/.mise/tasks/check.sh new file mode 100755 index 0000000..af5baef --- /dev/null +++ b/.mise/tasks/check.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +#MISE description = "Full validation: lint + test + build (the CI gate)." +# check.sh — the full local validation gate (lint + test + build). +# +# Invoked by `mise run check`. This is the SAME sequence CI runs, so a green +# `mise run check` locally predicts a green CI run (initial prompt step 24). +# +# Kept as a script (not a one-line mise task) because it chains three nx +# invocations and prints section headers. +# +# Docs: docs/monorepo/mise.md +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +echo "==> [1/3] lint" +nx run-many --target=lint --all + +echo "==> [2/3] test" +nx run-many --target=test --all + +echo "==> [3/3] build" +nx run-many --target=build --all + +echo "==> check passed" diff --git a/.mise/tasks/package-release.sh b/.mise/tasks/package-release.sh new file mode 100755 index 0000000..e844bf3 --- /dev/null +++ b/.mise/tasks/package-release.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# package-release.sh — placeholder packaging/release step for publishable packages. +# +# Invoked by a package's `release` run-script (e.g. `@farish/sdk`). Real +# publishing wiring (npm / ghcr.io) lands with the CI work in steps 24/27 — +# for now this script proves the `release` target exists in the nx graph and +# documents the contract. +# +# Usage: package-release.sh +# Docs: docs/monorepo/nx.md +set -euo pipefail + +PACKAGE="${1:?usage: package-release.sh }" + +echo "==> release: ${PACKAGE}" +echo " packaging + publish is a placeholder until CI is wired (prompt step 24/27)." +echo "==> release placeholder complete for ${PACKAGE}" diff --git a/.mise/tasks/publish-api.sh b/.mise/tasks/publish-api.sh new file mode 100755 index 0000000..6bddc98 --- /dev/null +++ b/.mise/tasks/publish-api.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# publish-api.sh — build and publish the @farish/api container image to ghcr.io. +# +# This is the `release` run-script for services/api. Publishing always targets +# the GitHub Container Registry, ghcr.io (initial prompt step 26). +# +# STUB STATUS: this script intentionally does NOT push by default. The push is +# gated behind PUBLISH=true so it cannot fire accidentally — wiring it into a +# release workflow with registry credentials is a later prompt step. Run +# without PUBLISH to validate the image builds; run with PUBLISH=true (after +# `docker login ghcr.io`) to actually publish. +# +# Usage: +# .mise/tasks/publish-api.sh # build only (validation) +# PUBLISH=true .mise/tasks/publish-api.sh # build + push to ghcr.io +# +# Docs: infra/ghcr.md +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +# ghcr.io image coordinates. OWNER defaults to the farish GitHub org/user. +OWNER="${GHCR_OWNER:-nsheaps}" +IMAGE="ghcr.io/${OWNER}/farish-api" +TAG="${IMAGE_TAG:-dev}" + +echo "==> Building ${IMAGE}:${TAG}" +if ! command -v docker >/dev/null 2>&1; then + echo "ERROR: docker is not installed — cannot build the image." >&2 + echo " Install Docker, or run this from a CI runner that has it." >&2 + exit 1 +fi + +docker build -f services/api/Dockerfile -t "${IMAGE}:${TAG}" . + +if [ "${PUBLISH:-}" = "true" ]; then + echo "==> Publishing ${IMAGE}:${TAG} to ghcr.io" + echo " (requires a prior: docker login ghcr.io)" + docker push "${IMAGE}:${TAG}" + echo "==> Published ${IMAGE}:${TAG}" +else + echo "==> Build complete. Set PUBLISH=true to push to ghcr.io." +fi diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..7310fdb --- /dev/null +++ b/Tiltfile @@ -0,0 +1,66 @@ +# Tiltfile — local development orchestration for farish. +# +# Tilt (https://tilt.dev) brings the whole local stack up with one command: +# +# tilt up +# +# farish is a browser-only app deployed to GitHub Pages, so local dev uses +# NATIVE PROCESSES ONLY — no containers, no Kubernetes. Every resource below is +# a `local_resource` running a plain process on the host (initial prompt +# step 26). Container images are built only for publishing (see infra/ghcr.md). +# +# Resources: +# deps-build — builds the workspace libraries the API + web app import. +# api — the @farish/api server (Bun), serving /health and /models. +# web — the @farish/web Vite dev server (Vue 3 + Vuetify), with HMR. +# +# Docs: docs/monorepo/tilt.md + +# --- Ports -------------------------------------------------------------------- +# Keep API_PORT in sync with apps/web/vite.config.ts (the dev proxy target). +API_PORT = '8787' +WEB_PORT = '5173' + +# --- deps-build --------------------------------------------------------------- +# The API server and the web app import the @farish/api-contract and +# @farish/mock-data libraries, which resolve to their built dist/ output. Build +# them once up front; re-runs are cheap because nx caches unchanged tasks. +local_resource( + 'deps-build', + cmd='nx run-many --target=build --projects=api-contract,mock-data', + deps=['lib/api-contract/src', 'lib/mock-data/src'], + labels=['build'], +) + +# --- api ---------------------------------------------------------------------- +# The development API server. `serve_cmd` keeps the process running; Tilt +# restarts it when a watched file under services/api/src changes. The readiness +# probe polls GET /health so dependent resources wait until the API is live. +local_resource( + 'api', + serve_cmd='bun run --watch services/api/src/server.ts', + serve_env={'PORT': API_PORT}, + deps=['services/api/src'], + resource_deps=['deps-build'], + readiness_probe=probe( + period_secs=2, + http_get=http_get_action(port=int(API_PORT), path='/health'), + ), + links=[link('http://localhost:%s/health' % API_PORT, 'API health')], + labels=['backend'], +) + +# --- web ---------------------------------------------------------------------- +# The Vue dev server. Vite provides hot-module reload, so Tilt only needs to +# start it once — it does not watch files itself. It depends on `api` so the +# /api proxy target is up before the browser app loads. +local_resource( + 'web', + serve_cmd='bun run --cwd apps/web dev -- --port %s' % WEB_PORT, + serve_env={'API_PORT': API_PORT}, + resource_deps=['deps-build', 'api'], + links=[link('http://localhost:%s' % WEB_PORT, 'farish web app')], + labels=['frontend'], +) + +print('farish dev stack — API on :%s, web on :%s' % (API_PORT, WEB_PORT)) diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000..a721bd5 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,20 @@ +{ + "name": "@farish/cli", + "version": "0.0.0", + "private": true, + "description": "Dummy CLI app — executes and exits. Exercises the nx graph as a leaf consumer.", + "type": "module", + "bin": { + "farish-cli": "./dist/main.js" + }, + "scripts": { + "lint": "biome lint ./src", + "format": "biome check --write ./src", + "test": "bun test", + "build": "tsc -p tsconfig.json", + "start": "bun run src/main.ts" + }, + "dependencies": { + "@farish/sdk": "workspace:*" + } +} diff --git a/apps/cli/project.json b/apps/cli/project.json new file mode 100644 index 0000000..05113b6 --- /dev/null +++ b/apps/cli/project.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "cli", + "projectType": "application", + "sourceRoot": "apps/cli/src", + "tags": ["scope:app"], + "targets": { + "lint": { "executor": "nx:run-script", "options": { "script": "lint" } }, + "format": { "executor": "nx:run-script", "options": { "script": "format" } }, + "test": { "executor": "nx:run-script", "options": { "script": "test" } }, + "build": { "executor": "nx:run-script", "options": { "script": "build" } } + } +} diff --git a/apps/cli/src/main.test.ts b/apps/cli/src/main.test.ts new file mode 100644 index 0000000..0b93d2c --- /dev/null +++ b/apps/cli/src/main.test.ts @@ -0,0 +1,8 @@ +import { describe, expect, test } from 'bun:test'; +import { describeProject } from '@farish/sdk'; + +describe('@farish/cli', () => { + test('renders the SDK status line it would print', () => { + expect(describeProject()).toContain('farish'); + }); +}); diff --git a/apps/cli/src/main.ts b/apps/cli/src/main.ts new file mode 100644 index 0000000..c8d64bf --- /dev/null +++ b/apps/cli/src/main.ts @@ -0,0 +1,14 @@ +/** + * @farish/cli — dummy CLI app. + * + * Executes and exits. Depends on `@farish/sdk`, which transitively pulls in + * both leaf libraries — so `nx build cli` builds core + math + sdk first. + */ +import { describeProject } from '@farish/sdk'; + +function main(): void { + // Allowed: `apps/**` is exempt from the noConsole lint rule (see biome.json). + console.log(describeProject()); +} + +main(); diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 0000000..246fb43 --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "src/**/*.test.ts"], + "references": [{ "path": "../../packages/sdk" }] +} diff --git a/apps/web/e2e/pages.spec.ts b/apps/web/e2e/pages.spec.ts new file mode 100644 index 0000000..3c7e6b9 --- /dev/null +++ b/apps/web/e2e/pages.spec.ts @@ -0,0 +1,50 @@ +/** + * farish page-screenshot suite. + * + * Initial prompt step 28: CI "always takes pictures on main of every page". + * This spec is the "well-defined page tests matching the page specs" — it + * iterates the `docs/pages/INDEX.md`-derived manifest (`pages.ts`) and, for + * every page with an existing route, navigates to it and captures a + * full-page screenshot. Playwright records a video of each test automatically + * (see `video: 'on'` in `playwright.config.ts`). + * + * Screenshots are written to `e2e/output/screenshots/.png`. The CI + * workflow (`.github/workflows/pages.yml`) copies that directory onto a + * date-based path and publishes it to GitHub Pages. + * + * Each test is named after the page slug + spec reference so a failing run + * points straight at the page spec it validates. + */ +import { mkdir } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; +import { BUILT_PAGES } from './pages'; + +/** Absolute path of the `e2e/` directory (ESM-safe `__dirname` equivalent). */ +const E2E_DIR = dirname(fileURLToPath(import.meta.url)); + +/** Directory the screenshot PNGs are written into. */ +const SCREENSHOT_DIR = join(E2E_DIR, 'output', 'screenshots'); + +test.beforeAll(async () => { + await mkdir(SCREENSHOT_DIR, { recursive: true }); +}); + +for (const page of BUILT_PAGES) { + test(`page: ${page.slug} — screenshot (${page.specRef})`, async ({ page: browserPage }) => { + // Navigate to the page's hash route and wait for the SPA to settle. + await browserPage.goto(page.route, { waitUntil: 'networkidle' }); + + // The Vuetify app root must be present — a minimal "the page rendered" + // assertion that fails loudly if the route is broken. + await expect(browserPage.locator('.v-application')).toBeVisible(); + + // Capture the full page. `animations: 'disabled'` keeps the image stable. + await browserPage.screenshot({ + path: join(SCREENSHOT_DIR, `${page.slug}.png`), + fullPage: true, + animations: 'disabled', + }); + }); +} diff --git a/apps/web/e2e/pages.ts b/apps/web/e2e/pages.ts new file mode 100644 index 0000000..914cbd4 --- /dev/null +++ b/apps/web/e2e/pages.ts @@ -0,0 +1,114 @@ +/** + * Page manifest for the farish screenshot suite. + * + * Every entry corresponds to a page in `docs/pages/INDEX.md`. The screenshot + * test (`pages.spec.ts`) iterates this list and exercises only the entries + * whose `built` flag is `true` — i.e. routes that actually exist in + * `src/router/index.ts` today. + * + * As later prompt steps (30–32) build the real pages, flip `built` to `true` + * and the CI screenshot pipeline (step 28) picks the new page up automatically + * with no workflow change. This keeps the harness "well-defined page tests + * matching the page specs" (initial prompt, step 28) while starting lean. + * + * `route` uses hash paths because the router uses `createWebHashHistory` + * (static GitHub Pages deployment — see src/router/index.ts). + */ + +/** A single page under screenshot test. */ +export interface PageEntry { + /** Stable slug — matches the `docs/pages//` spec directory. */ + readonly slug: string; + /** Human-readable page name. */ + readonly name: string; + /** Hash route to navigate to (relative to the app origin). */ + readonly route: string; + /** Path to the page's spec, for traceability in test names. */ + readonly specRef: string; + /** Whether a route for this page exists yet. Only `true` pages are tested. */ + readonly built: boolean; +} + +/** + * All 11 farish pages from `docs/pages/INDEX.md`. + * + * Only `home` and `explore` have routes today (the framework skeleton from + * step 26). The rest are listed with `built: false` so the suite is the + * single source of truth for "every page" and scales without edits here. + */ +export const PAGES: readonly PageEntry[] = [ + { slug: 'home', name: 'Home', route: '#/', specRef: 'docs/pages/home/SPEC.md', built: true }, + { + slug: 'explore', + name: 'Explore', + route: '#/explore', + specRef: 'docs/pages/explore/SPEC.md', + built: true, + }, + { + slug: 'leaderboards', + name: 'Leaderboards', + route: '#/leaderboards', + specRef: 'docs/pages/leaderboards/SPEC.md', + built: false, + }, + { + slug: 'generate', + name: 'Generate', + route: '#/generate', + specRef: 'docs/pages/generate/SPEC.md', + built: false, + }, + { + slug: 'model-detail', + name: 'Model Detail', + route: '#/model/example', + specRef: 'docs/pages/model-detail/SPEC.md', + built: false, + }, + { + slug: 'my-library', + name: 'My Library', + route: '#/library', + specRef: 'docs/pages/my-library/SPEC.md', + built: false, + }, + { + slug: 'profile', + name: 'Profile', + route: '#/u/example', + specRef: 'docs/pages/profile/SPEC.md', + built: false, + }, + { + slug: 'settings', + name: 'Settings', + route: '#/settings', + specRef: 'docs/pages/settings/SPEC.md', + built: false, + }, + { + slug: 'about', + name: 'About', + route: '#/about', + specRef: 'docs/pages/about/SPEC.md', + built: false, + }, + { + slug: 'coming-soon', + name: 'Coming Soon', + route: '#/coming-soon', + specRef: 'docs/pages/coming-soon/SPEC.md', + built: false, + }, + { + slug: 'not-found', + name: 'Not Found', + route: '#/no-such-route', + specRef: 'docs/pages/not-found/SPEC.md', + built: false, + }, +]; + +/** The subset of pages that have a route today and are screenshot-tested. */ +export const BUILT_PAGES: readonly PageEntry[] = PAGES.filter((p) => p.built); diff --git a/apps/web/e2e/playwright.config.ts b/apps/web/e2e/playwright.config.ts new file mode 100644 index 0000000..bb8e988 --- /dev/null +++ b/apps/web/e2e/playwright.config.ts @@ -0,0 +1,97 @@ +/** + * Playwright config for the farish page-screenshot suite. + * + * Initial prompt step 28: "as soon as you have a server that launches, each + * validation loop must (using CI) automatically take screenshots of each page + * ... + recording videos". This config is the runner for that requirement. + * + * Playwright Test is used as the test runner. The prompt mentions "playwright + * + jest"; Playwright Test ships its own jest-style `expect`/`test` API, so a + * separate jest install would be redundant — Playwright Test fills both roles. + * + * - `webServer` builds nothing itself; it runs `vite preview` against the + * already-built `dist/` (the CI workflow runs `nx run web:build` first, and + * the local `e2e` npm script chains the build). `vite preview` serves the + * exact production bundle that ships to GitHub Pages — so screenshots match + * what users see. + * - `outputDir` collects per-test artifacts (videos, traces). The screenshot + * PNGs themselves are written explicitly by `pages.spec.ts` into + * `e2e/output/screenshots/` so the CI workflow can publish them on a + * date-based path. + * - `video: 'on'` records every test; the prompt requires recorded videos. + * + * CI hardening: + * - `webServer.cwd: '..'` sets the cwd for the preview server to `apps/web/` + * (the package root) regardless of where playwright was invoked. Without + * this, Playwright spawns the webServer command from `apps/web/e2e/` + * (configDir). Bun's package-script resolution may or may not walk up to + * find the parent package.json on a fresh runner — making this explicit + * removes the ambiguity entirely. + * - `webServer.timeout: 120_000` — generous for a cold GH Actions runner that + * starts Chromium for the first time after the browser install step. + * - `forbidOnly` — CI-standard guard: fails the run if `.only` is accidentally + * left in a test file. + * - `launchOptions.args` — `--disable-dev-shm-usage` prevents Chromium crashes + * in containers / GH runners where /dev/shm is limited to 64 MB. `--no-sandbox` + * is added explicitly here even though Playwright also adds it by default + * (chromiumSandbox defaults to false) — defence in depth. + */ +import { defineConfig, devices } from '@playwright/test'; + +/** Port the preview server listens on during the e2e run. */ +const PREVIEW_PORT = 4173; + +export default defineConfig({ + testDir: '.', + testMatch: '**/*.spec.ts', + // Standard CI guard: abort the run if a test.only leaked into the suite. + forbidOnly: !!process.env.CI, + // Screenshots must be deterministic — no retries, single worker, fixed viewport. + retries: 0, + workers: 1, + // Per-test timeout: generous for a cold runner, tight enough to surface hangs. + timeout: 60_000, + // All artifacts (videos, traces, the screenshot PNGs) live under e2e/output/. + outputDir: 'output/test-results', + reporter: [ + ['list'], + // HTML report is published to GitHub Pages alongside the screenshots. + ['html', { outputFolder: 'output/report', open: 'never' }], + ], + use: { + baseURL: `http://localhost:${PREVIEW_PORT}/`, + viewport: { width: 1280, height: 800 }, + // Record a video for every test (initial prompt step 28). + video: 'on', + trace: 'retain-on-failure', + // Explicit Chromium flags for CI (Docker / GitHub Actions). + // --no-sandbox : Playwright adds this by default (chromiumSandbox !== true), + // but we set it explicitly as defence in depth. + // --disable-dev-shm-usage : prevents crashes where /dev/shm is 64 MB (Docker + // / GH Actions containers). + launchOptions: { + args: ['--no-sandbox', '--disable-dev-shm-usage'], + }, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + // Serve the production build. `reuseExistingServer` lets a developer point + // the suite at an already-running preview during local iteration. + // + // `cwd: '..'` resolves to apps/web/ (the package root) — Playwright's + // webServer plugin defaults cwd to configDir (apps/web/e2e/), which causes + // `bun run preview` to depend on Bun's directory-walking behavior to find the + // parent package.json. Setting cwd explicitly makes the command deterministic + // across all environments and Bun versions. + webServer: { + command: `bun run preview -- --port ${PREVIEW_PORT} --strictPort`, + url: `http://localhost:${PREVIEW_PORT}/`, + cwd: '..', + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, +}); diff --git a/apps/web/e2e/tsconfig.json b/apps/web/e2e/tsconfig.json new file mode 100644 index 0000000..7e6f3d7 --- /dev/null +++ b/apps/web/e2e/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "//": "Type-checking config for the Playwright e2e suite. Separate from the app tsconfig because the suite runs in Node (uses __dirname, node:fs) rather than the browser. Not part of the root tsc solution — Playwright compiles its own specs.", + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022"], + "types": ["node"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "composite": false + }, + "include": ["**/*.ts"], + "exclude": ["output", "node_modules"] +} diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..a46f1ff --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,12 @@ + + + + + + farish — AI 3D model generator + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..56b8b48 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,33 @@ +{ + "name": "@farish/web", + "version": "0.0.0", + "private": true, + "description": "farish browser app — Vue 3 + Vuetify. Vite dev server with HMR; static production build deployable to GitHub Pages.", + "type": "module", + "scripts": { + "lint": "biome lint ./src ./e2e", + "format": "biome check --write ./src ./e2e", + "test": "vitest run", + "build": "vite build", + "dev": "vite", + "preview": "vite preview", + "e2e": "vite build && playwright test --config e2e/playwright.config.ts" + }, + "dependencies": { + "@farish/api-contract": "workspace:*", + "@farish/mock-data": "workspace:*", + "@mdi/font": "^7.4.47", + "vue": "^3.5.34", + "vue-router": "^5.0.7", + "vuetify": "^4.0.7" + }, + "devDependencies": { + "@playwright/test": "1.50.1", + "@vitejs/plugin-vue": "^6.0.7", + "@vue/test-utils": "^2.4.10", + "jsdom": "^29.1.1", + "vite": "^8.0.13", + "vite-plugin-vuetify": "^2.1.3", + "vitest": "^4.1.7" + } +} diff --git a/apps/web/project.json b/apps/web/project.json new file mode 100644 index 0000000..6429683 --- /dev/null +++ b/apps/web/project.json @@ -0,0 +1,20 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "web", + "projectType": "application", + "sourceRoot": "apps/web/src", + "tags": ["scope:app"], + "//": "Build/test go through vite + vitest (Vue SFCs are not plain TS), so the targets run package.json scripts via nx:run-script — identical executor to every other package. The `e2e` target runs the Playwright screenshot suite; it is intentionally NOT part of `mise run check` (it needs browsers + a served build) — the CI screenshot workflow invokes it directly.", + "targets": { + "lint": { "executor": "nx:run-script", "options": { "script": "lint" } }, + "format": { "executor": "nx:run-script", "options": { "script": "format" } }, + "test": { "executor": "nx:run-script", "options": { "script": "test" } }, + "build": { "executor": "nx:run-script", "options": { "script": "build" } }, + "e2e": { + "executor": "nx:run-script", + "options": { "script": "e2e" }, + "//": "dependsOn ^build so workspace lib dist/ artefacts exist before vite build runs (cold CI has no pre-built dist/).", + "dependsOn": ["^build"] + } + } +} diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue new file mode 100644 index 0000000..7052a61 --- /dev/null +++ b/apps/web/src/App.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/apps/web/src/api/client.test.ts b/apps/web/src/api/client.test.ts new file mode 100644 index 0000000..1906b5c --- /dev/null +++ b/apps/web/src/api/client.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; +import { getHealth, listModels } from './client.ts'; + +/** Build a minimal `fetch` Response stub. */ +function jsonResponse(body: unknown, ok = true, status = 200): Response { + return { + ok, + status, + json: () => Promise.resolve(body), + } as Response; +} + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('@farish/web — API client', () => { + test('getHealth requests /health under the /api dev proxy prefix', async () => { + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue( + jsonResponse({ status: 'ok', service: 'api', version: '0.0.0', uptimeSeconds: 1 }), + ); + + const health = await getHealth(); + + expect(fetchMock).toHaveBeenCalledWith('/api/health'); + expect(health.status).toBe('ok'); + }); + + test('listModels requests /models and returns the model array', async () => { + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue(jsonResponse({ models: [], total: 0 })); + + const result = await listModels(); + + expect(fetchMock).toHaveBeenCalledWith('/api/models'); + expect(result.models).toEqual([]); + }); + + test('a non-ok response rejects with a descriptive error', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({}, false, 500)); + await expect(getHealth()).rejects.toThrow('API request failed: 500'); + }); +}); diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts new file mode 100644 index 0000000..3d60f9f --- /dev/null +++ b/apps/web/src/api/client.ts @@ -0,0 +1,43 @@ +/** + * @farish/web — typed API client. + * + * A thin `fetch` wrapper, typed end-to-end against `@farish/api-contract` so + * the browser app and the API server can never disagree about a payload shape. + * + * Base URL resolution: + * - Development: defaults to `/api`, which the Vite dev server proxies to the + * local API process (see `vite.config.ts`). + * - Production: set `VITE_API_BASE_URL` at build time to the deployed API + * origin. While farish is a browser-only GitHub Pages site with no backend, + * callers handle a failed request gracefully (the example view shows this). + */ +import { + type HealthResponse, + type ListModelsResponse, + ROUTES, +} from '@farish/api-contract'; + +/** Resolve the API base URL from the build-time env, defaulting to the dev proxy. */ +function apiBaseUrl(): string { + const fromEnv = import.meta.env.VITE_API_BASE_URL; + return typeof fromEnv === 'string' && fromEnv.length > 0 ? fromEnv : '/api'; +} + +/** Perform a GET request and parse the JSON body as `T`. */ +async function getJson(path: string): Promise { + const res = await fetch(`${apiBaseUrl()}${path}`); + if (!res.ok) { + throw new Error(`API request failed: ${String(res.status)} ${path}`); + } + return (await res.json()) as T; +} + +/** `GET /health` — liveness probe. */ +export function getHealth(): Promise { + return getJson(ROUTES.health); +} + +/** `GET /models` — the example model list (stubbed server-side in step 26). */ +export function listModels(): Promise { + return getJson(ROUTES.listModels); +} diff --git a/apps/web/src/components/ComingSoon.test.ts b/apps/web/src/components/ComingSoon.test.ts new file mode 100644 index 0000000..63b85be --- /dev/null +++ b/apps/web/src/components/ComingSoon.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'vitest'; +import { h } from 'vue'; +import { mountWithVuetify } from '../test/mount.ts'; +import ComingSoon from './ComingSoon.vue'; + +describe('ComingSoon', () => { + test('renders the target page name in the headline', () => { + const wrapper = mountWithVuetify({ + render: () => h(ComingSoon, { targetPageName: 'Explore' }), + }); + expect(wrapper.text()).toContain('Explore is coming soon'); + }); + + test('renders the default note when none is given', () => { + const wrapper = mountWithVuetify({ + render: () => h(ComingSoon, { targetPageName: 'Leaderboards' }), + }); + expect(wrapper.text()).toContain('needs a shared backend'); + }); + + test('renders a custom note when provided', () => { + const wrapper = mountWithVuetify({ + render: () => + h(ComingSoon, { targetPageName: 'Profile', note: 'Custom note here.' }), + }); + expect(wrapper.text()).toContain('Custom note here.'); + }); + + test('renders the ghost wireframe slot content', () => { + const wrapper = mountWithVuetify({ + render: () => + h(ComingSoon, { targetPageName: 'Explore' }, () => + h('p', { class: 'ghost-marker' }, 'ghost content'), + ), + }); + expect(wrapper.find('.ghost-marker').exists()).toBe(true); + expect(wrapper.find('[data-testid="ghost-wireframe"]').exists()).toBe(true); + }); +}); diff --git a/apps/web/src/components/ComingSoon.vue b/apps/web/src/components/ComingSoon.vue new file mode 100644 index 0000000..e95058a --- /dev/null +++ b/apps/web/src/components/ComingSoon.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/apps/web/src/components/GhostModelGrid.test.ts b/apps/web/src/components/GhostModelGrid.test.ts new file mode 100644 index 0000000..1d212b8 --- /dev/null +++ b/apps/web/src/components/GhostModelGrid.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from 'vitest'; +import { mountWithVuetify } from '../test/mount.ts'; +import GhostModelGrid from './GhostModelGrid.vue'; + +describe('GhostModelGrid', () => { + test('renders a card for each of the eight mock models', () => { + const wrapper = mountWithVuetify(GhostModelGrid); + expect(wrapper.findAllComponents({ name: 'VCard' })).toHaveLength(8); + }); + + test('thumbnails use offline data-URI placeholder images', () => { + const wrapper = mountWithVuetify(GhostModelGrid); + const images = wrapper.findAllComponents({ name: 'VImg' }); + expect(images.length).toBeGreaterThan(0); + for (const img of images) { + expect(String(img.props('src')).startsWith('data:image/svg+xml,')).toBe(true); + } + }); +}); diff --git a/apps/web/src/components/GhostModelGrid.vue b/apps/web/src/components/GhostModelGrid.vue new file mode 100644 index 0000000..0edaa47 --- /dev/null +++ b/apps/web/src/components/GhostModelGrid.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts new file mode 100644 index 0000000..ffdfb2c --- /dev/null +++ b/apps/web/src/main.ts @@ -0,0 +1,13 @@ +/** + * @farish/web — application entrypoint. + * + * Creates the Vue app, registers the Vuetify design system and the router, + * and mounts it. Vite serves this in development with HMR and bundles it into + * a static `dist/` for GitHub Pages in production. + */ +import { createApp } from 'vue'; +import App from './App.vue'; +import { vuetify } from './plugins/vuetify.ts'; +import { router } from './router/index.ts'; + +createApp(App).use(vuetify).use(router).mount('#app'); diff --git a/apps/web/src/plugins/vuetify.ts b/apps/web/src/plugins/vuetify.ts new file mode 100644 index 0000000..0d519f5 --- /dev/null +++ b/apps/web/src/plugins/vuetify.ts @@ -0,0 +1,29 @@ +/** + * Vuetify plugin setup for @farish/web. + * + * One `createVuetify` call configures the design system (Vuetify 4 / Material + * Design 3) for the whole app. A custom `farish` theme moves the palette off + * the default Material colours, as recommended in the design-system research + * note (`docs/research/design-system-choice.md`). + */ +import '@mdi/font/css/materialdesignicons.css'; +import 'vuetify/styles'; +import { createVuetify } from 'vuetify'; + +/** The shared Vuetify instance — registered on the app in `main.ts`. */ +export const vuetify = createVuetify({ + theme: { + defaultTheme: 'farish', + themes: { + farish: { + dark: false, + colors: { + primary: '#5b3fb8', + secondary: '#00897b', + surface: '#ffffff', + background: '#f5f4fa', + }, + }, + }, + }, +}); diff --git a/apps/web/src/router/index.ts b/apps/web/src/router/index.ts new file mode 100644 index 0000000..9f45c70 --- /dev/null +++ b/apps/web/src/router/index.ts @@ -0,0 +1,61 @@ +/** + * @farish/web — client-side router. + * + * Uses hash history (`createWebHashHistory`) so the app works as a static + * GitHub Pages deployment with no server-side rewrite rules: every route lives + * under `index.html#/...`. + * + * All 11 pages from `docs/pages/INDEX.md` are registered here (step 30). + * Backend-dependent pages (explore, leaderboards, profile) render via the + * ComingSoon mechanism at their own routes. + * + * Routes: + * / → HomeView (static) + * /explore → ExploreComingSoonView (backend → Coming Soon) + * /leaderboards → LeaderboardsComingSoonView (backend → Coming Soon) + * /generate → GenerateView (browser-only) + * /m/:modelId → ModelDetailView (browser-only viewer + backend social) + * /library → MyLibraryView (browser-only) + * /u/:username → ProfileComingSoonView (backend → Coming Soon) + * /settings → SettingsView (browser-only) + * /about → AboutView (static) + * /coming-soon → ComingSoonView (system — direct fallback) + * /:pathMatch(.*)* → NotFoundView (system — catch-all 404) + */ +import { createRouter, createWebHashHistory } from 'vue-router'; +import AboutView from '../views/AboutView.vue'; +import ComingSoonView from '../views/ComingSoonView.vue'; +import ExploreComingSoonView from '../views/ExploreComingSoonView.vue'; +import GenerateView from '../views/GenerateView.vue'; +import HomeView from '../views/HomeView.vue'; +import LeaderboardsComingSoonView from '../views/LeaderboardsComingSoonView.vue'; +import ModelDetailView from '../views/ModelDetailView.vue'; +import MyLibraryView from '../views/MyLibraryView.vue'; +import NotFoundView from '../views/NotFoundView.vue'; +import ProfileComingSoonView from '../views/ProfileComingSoonView.vue'; +import SettingsView from '../views/SettingsView.vue'; + +/** The farish router instance. */ +export const router = createRouter({ + history: createWebHashHistory(), + routes: [ + // ── Discover ──────────────────────────────────────────────────────────── + { path: '/', name: 'home', component: HomeView }, + { path: '/explore', name: 'explore', component: ExploreComingSoonView }, + { path: '/leaderboards', name: 'leaderboards', component: LeaderboardsComingSoonView }, + + // ── Create ─────────────────────────────────────────────────────────────── + { path: '/generate', name: 'generate', component: GenerateView }, + { path: '/m/:modelId', name: 'model-detail', component: ModelDetailView }, + + // ── Account ────────────────────────────────────────────────────────────── + { path: '/library', name: 'my-library', component: MyLibraryView }, + { path: '/u/:username', name: 'profile', component: ProfileComingSoonView }, + { path: '/settings', name: 'settings', component: SettingsView }, + + // ── System ─────────────────────────────────────────────────────────────── + { path: '/about', name: 'about', component: AboutView }, + { path: '/coming-soon', name: 'coming-soon', component: ComingSoonView }, + { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView }, + ], +}); diff --git a/apps/web/src/shims-vue.d.ts b/apps/web/src/shims-vue.d.ts new file mode 100644 index 0000000..a9d25fa --- /dev/null +++ b/apps/web/src/shims-vue.d.ts @@ -0,0 +1,11 @@ +/** + * Ambient declaration so TypeScript understands `*.vue` single-file component + * imports. Vue's SFC compiler turns each `.vue` file into a component; this + * shim gives the type system a matching module declaration. + */ +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + + const component: DefineComponent, Record, unknown>; + export default component; +} diff --git a/apps/web/src/test/mount.ts b/apps/web/src/test/mount.ts new file mode 100644 index 0000000..9594f27 --- /dev/null +++ b/apps/web/src/test/mount.ts @@ -0,0 +1,32 @@ +/** + * @farish/web — shared test mounting helper. + * + * Vuetify components require the Vuetify plugin to be installed on the app, and + * many also need a router. This helper wires both so individual component + * tests stay terse. + */ +import { mount, type VueWrapper } from '@vue/test-utils'; +import type { Component } from 'vue'; +import { createVuetify } from 'vuetify'; +import * as components from 'vuetify/components'; +import * as directives from 'vuetify/directives'; +import { createRouter, createWebHashHistory, type Router } from 'vue-router'; + +/** Build a throwaway router with the two framework routes for tests. */ +function testRouter(): Router { + return createRouter({ + history: createWebHashHistory(), + routes: [ + { path: '/', name: 'home', component: { template: '
' } }, + { path: '/explore', name: 'explore', component: { template: '
' } }, + ], + }); +} + +/** Mount a component with Vuetify + a router installed. */ +export function mountWithVuetify(component: Component): VueWrapper { + const vuetify = createVuetify({ components, directives }); + return mount(component, { + global: { plugins: [vuetify, testRouter()] }, + }); +} diff --git a/apps/web/src/test/setup.ts b/apps/web/src/test/setup.ts new file mode 100644 index 0000000..c7984b0 --- /dev/null +++ b/apps/web/src/test/setup.ts @@ -0,0 +1,52 @@ +/** + * @farish/web — Vitest global setup. + * + * jsdom does not implement every browser API that Vuetify components touch + * (`ResizeObserver`, `visualViewport`, `matchMedia`). These no-op polyfills let + * Vuetify components mount under jsdom without throwing. They are test-only — + * a real browser supplies the genuine implementations. + */ + +// ResizeObserver — used by Vuetify layout/size composables. +if (!('ResizeObserver' in globalThis)) { + class ResizeObserverStub { + observe(): void {} + unobserve(): void {} + disconnect(): void {} + } + globalThis.ResizeObserver = ResizeObserverStub as unknown as typeof ResizeObserver; +} + +// visualViewport — read by Vuetify's overlay location strategies. +if (!('visualViewport' in window) || window.visualViewport === null) { + Object.defineProperty(window, 'visualViewport', { + configurable: true, + value: { + width: 1024, + height: 768, + offsetLeft: 0, + offsetTop: 0, + scale: 1, + addEventListener: (): void => {}, + removeEventListener: (): void => {}, + }, + }); +} + +// matchMedia — used by Vuetify's display/breakpoint composable. +if (typeof window.matchMedia !== 'function') { + Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: (query: string): MediaQueryList => + ({ + matches: false, + media: query, + onchange: null, + addEventListener: (): void => {}, + removeEventListener: (): void => {}, + addListener: (): void => {}, + removeListener: (): void => {}, + dispatchEvent: (): boolean => false, + }) as unknown as MediaQueryList, + }); +} diff --git a/apps/web/src/views/AboutView.vue b/apps/web/src/views/AboutView.vue new file mode 100644 index 0000000..0fdfed9 --- /dev/null +++ b/apps/web/src/views/AboutView.vue @@ -0,0 +1,147 @@ + + + diff --git a/apps/web/src/views/ComingSoonView.vue b/apps/web/src/views/ComingSoonView.vue new file mode 100644 index 0000000..05c1886 --- /dev/null +++ b/apps/web/src/views/ComingSoonView.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web/src/views/ExploreComingSoonView.vue b/apps/web/src/views/ExploreComingSoonView.vue new file mode 100644 index 0000000..6e6577a --- /dev/null +++ b/apps/web/src/views/ExploreComingSoonView.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web/src/views/GenerateView.vue b/apps/web/src/views/GenerateView.vue new file mode 100644 index 0000000..9fdeb7f --- /dev/null +++ b/apps/web/src/views/GenerateView.vue @@ -0,0 +1,417 @@ + + + diff --git a/apps/web/src/views/HomeView.test.ts b/apps/web/src/views/HomeView.test.ts new file mode 100644 index 0000000..79df128 --- /dev/null +++ b/apps/web/src/views/HomeView.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from 'vitest'; +import { mountWithVuetify } from '../test/mount.ts'; +import HomeView from './HomeView.vue'; + +describe('HomeView — Home page stub', () => { + test('renders the hero section', () => { + const wrapper = mountWithVuetify(HomeView); + expect(wrapper.find('[data-testid="home-hero"]').exists()).toBe(true); + expect(wrapper.text()).toContain('farish'); + }); + + test('renders the Generate CTA that links to /generate', () => { + const wrapper = mountWithVuetify(HomeView); + const cta = wrapper.find('[data-testid="home-generate-cta"]'); + expect(cta.exists()).toBe(true); + }); + + test('renders the trending preview strip', () => { + const wrapper = mountWithVuetify(HomeView); + expect(wrapper.find('[data-testid="home-trending-strip"]').exists()).toBe(true); + }); +}); diff --git a/apps/web/src/views/HomeView.vue b/apps/web/src/views/HomeView.vue new file mode 100644 index 0000000..dfa8fe9 --- /dev/null +++ b/apps/web/src/views/HomeView.vue @@ -0,0 +1,193 @@ + + + diff --git a/apps/web/src/views/LeaderboardsComingSoonView.vue b/apps/web/src/views/LeaderboardsComingSoonView.vue new file mode 100644 index 0000000..cdfbc94 --- /dev/null +++ b/apps/web/src/views/LeaderboardsComingSoonView.vue @@ -0,0 +1,110 @@ + + + diff --git a/apps/web/src/views/ModelDetailView.vue b/apps/web/src/views/ModelDetailView.vue new file mode 100644 index 0000000..42ac075 --- /dev/null +++ b/apps/web/src/views/ModelDetailView.vue @@ -0,0 +1,277 @@ + + + diff --git a/apps/web/src/views/MyLibraryView.vue b/apps/web/src/views/MyLibraryView.vue new file mode 100644 index 0000000..6f815a2 --- /dev/null +++ b/apps/web/src/views/MyLibraryView.vue @@ -0,0 +1,209 @@ + + + diff --git a/apps/web/src/views/NotFoundView.vue b/apps/web/src/views/NotFoundView.vue new file mode 100644 index 0000000..d7ce47d --- /dev/null +++ b/apps/web/src/views/NotFoundView.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web/src/views/ProfileComingSoonView.vue b/apps/web/src/views/ProfileComingSoonView.vue new file mode 100644 index 0000000..2e72c03 --- /dev/null +++ b/apps/web/src/views/ProfileComingSoonView.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/web/src/views/SettingsView.vue b/apps/web/src/views/SettingsView.vue new file mode 100644 index 0000000..79b67dc --- /dev/null +++ b/apps/web/src/views/SettingsView.vue @@ -0,0 +1,256 @@ + + + diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..6079b21 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "//": "Editor / vue-tsc type-checking config for the Vue app. The app is BUILT by Vite (vite build), not tsc, so this project is intentionally absent from the root tsconfig.json solution. DOM + Vue SFC support is added on top of the strict workspace base.", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "jsx": "preserve", + "noEmit": true, + "composite": false + }, + "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts", "vitest.config.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..be87238 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,63 @@ +/** + * Vite config for @farish/web. + * + * - `@vitejs/plugin-vue` compiles Vue single-file components. + * - `vite-plugin-vuetify` enables Vuetify's automatic component tree-shaking. + * - `base` is read from `VITE_BASE` so the prod build can be deployed under a + * GitHub Pages project sub-path (e.g. `/farish/`); it defaults to `/`. + * - The dev server proxies `/api` to the local API server so the browser app + * calls the API same-origin (no CORS hop) during development. The API's + * `/api` prefix is stripped before forwarding. + * - `resolve.alias` maps every `@farish/*` workspace lib directly to its + * TypeScript source entry so `vite build` never needs pre-built `dist/` + * artefacts. This is the bulletproof fix for cold CI runners where no + * workspace lib has been compiled yet. The deps remain in package.json for + * nx-graph correctness and type-checking; Vite just bypasses the `dist/` + * redirect at bundle time. + */ +import vue from '@vitejs/plugin-vue'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; +import vuetify from 'vite-plugin-vuetify'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +/** Local API server port — keep in sync with the Tiltfile `api` resource. */ +const API_PORT = process.env.API_PORT ?? '8787'; + +export default defineConfig({ + base: process.env.VITE_BASE ?? '/', + plugins: [vue(), vuetify({ autoImport: true })], + resolve: { + alias: { + // Map every @farish/* workspace lib to its TypeScript source entry. + // Vite bundles from source so `dist/` artefacts are never required — + // the build is cold-safe on a fresh CI runner with no prior compilation. + '@farish/api-contract': resolve(__dirname, '../../lib/api-contract/src/index.ts'), + '@farish/mock-data': resolve(__dirname, '../../lib/mock-data/src/index.ts'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: `http://localhost:${API_PORT}`, + changeOrigin: true, + rewrite: (path: string): string => path.replace(/^\/api/, ''), + }, + }, + }, + // Explicitly disable the API proxy in preview mode (used by `vite preview` + // and the Playwright screenshot suite). Vite v8 falls back to `server.proxy` + // when `preview.proxy` is not set — leaving the proxy active in preview + // causes every API request to hit ECONNREFUSED (no API server in CI or + // during the screenshot run) and generates noisy proxy-error logs that can + // delay `networkidle` detection in Playwright. + preview: { + proxy: {}, + }, + build: { + outDir: 'dist', + }, +}); diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 0000000..bce54b1 --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,23 @@ +/** + * Vitest config for @farish/web. + * + * Vue components must be compiled before they can be unit-tested, so the test + * runner is Vitest (which reuses the Vite/Vue transform pipeline) rather than + * `bun test`. A jsdom environment provides the DOM that Vuetify components and + * `@vue/test-utils` mounting require. + */ +import vue from '@vitejs/plugin-vue'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [vue()], + test: { + environment: 'jsdom', + globals: true, + include: ['src/**/*.test.ts'], + // Polyfill the browser APIs Vuetify needs that jsdom lacks. + setupFiles: ['src/test/setup.ts'], + // Vuetify ships untranspiled ESM; let Vitest transform it. + server: { deps: { inline: ['vuetify'] } }, + }, +}); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..6fe4409 --- /dev/null +++ b/biome.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.json", "!**/dist/**", "!**/node_modules/**"] + }, + "plugins": ["./.biome/plugins/no-as-any.grit"], + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100, + "lineEnding": "lf" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "error", + "noConsole": { "level": "error", "options": { "allow": ["error", "warn"] } }, + "noDebugger": "error" + }, + "complexity": { + "noExcessiveCognitiveComplexity": "warn" + }, + "style": { + "useConst": "error", + "noNonNullAssertion": "error", + "useImportType": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "always", + "trailingCommas": "all" + } + }, + "overrides": [ + { + "includes": ["apps/**"], + "linter": { "rules": { "suspicious": { "noConsole": "off" } } } + } + ] +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..aa5d075 --- /dev/null +++ b/bun.lock @@ -0,0 +1,840 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "farish", + "devDependencies": { + "@biomejs/biome": "^2.2.0", + "@types/bun": "^1.3.0", + "marked": "15.0.6", + "nx": "^21.0.0", + "typescript": "^5.7.0", + }, + }, + "apps/cli": { + "name": "@farish/cli", + "version": "0.0.0", + "bin": { + "farish-cli": "./dist/main.js", + }, + "dependencies": { + "@farish/sdk": "workspace:*", + }, + }, + "apps/web": { + "name": "@farish/web", + "version": "0.0.0", + "dependencies": { + "@farish/api-contract": "workspace:*", + "@farish/mock-data": "workspace:*", + "@mdi/font": "^7.4.47", + "vue": "^3.5.34", + "vue-router": "^5.0.7", + "vuetify": "^4.0.7", + }, + "devDependencies": { + "@playwright/test": "1.50.1", + "@vitejs/plugin-vue": "^6.0.7", + "@vue/test-utils": "^2.4.10", + "jsdom": "^29.1.1", + "vite": "^8.0.13", + "vite-plugin-vuetify": "^2.1.3", + "vitest": "^4.1.7", + }, + }, + "lib/api-contract": { + "name": "@farish/api-contract", + "version": "0.0.0", + }, + "lib/core": { + "name": "@farish/core", + "version": "0.0.0", + }, + "lib/math": { + "name": "@farish/math", + "version": "0.0.0", + }, + "lib/mock-data": { + "name": "@farish/mock-data", + "version": "0.0.0", + "dependencies": { + "@farish/api-contract": "workspace:*", + }, + }, + "packages/sdk": { + "name": "@farish/sdk", + "version": "0.0.0", + "dependencies": { + "@farish/core": "workspace:*", + "@farish/math": "workspace:*", + }, + }, + "services/api": { + "name": "@farish/api", + "version": "0.0.0", + "dependencies": { + "@farish/api-contract": "workspace:*", + "@farish/mock-data": "workspace:*", + }, + }, + "services/echo-service": { + "name": "@farish/echo-service", + "version": "0.0.0", + "dependencies": { + "@farish/core": "workspace:*", + }, + }, + }, + "packages": { + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.11", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@csstools/css-calc": "^3.2.0", "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg=="], + + "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@7.1.1", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1" } }, "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ=="], + + "@asamuzakjp/generational-cache": ["@asamuzakjp/generational-cache@1.0.1", "", {}, "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg=="], + + "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="], + + "@babel/generator": ["@babel/generator@8.0.0-rc.5", "", { "dependencies": { "@babel/parser": "^8.0.0-rc.5", "@babel/types": "^8.0.0-rc.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.5", "", {}, "sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.0-rc.5", "", {}, "sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg=="], + + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@babel/types": ["@babel/types@8.0.0-rc.5", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0-rc.5", "@babel/helper-validator-identifier": "^8.0.0-rc.5" } }, "sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA=="], + + "@biomejs/biome": ["@biomejs/biome@2.4.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.15", "@biomejs/cli-darwin-x64": "2.4.15", "@biomejs/cli-linux-arm64": "2.4.15", "@biomejs/cli-linux-arm64-musl": "2.4.15", "@biomejs/cli-linux-x64": "2.4.15", "@biomejs/cli-linux-x64-musl": "2.4.15", "@biomejs/cli-win32-arm64": "2.4.15", "@biomejs/cli-win32-x64": "2.4.15" }, "bin": { "biome": "bin/biome" } }, "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.15", "", { "os": "linux", "cpu": "x64" }, "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.15", "", { "os": "linux", "cpu": "x64" }, "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.15", "", { "os": "win32", "cpu": "x64" }, "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ=="], + + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], + + "@csstools/color-helpers": ["@csstools/color-helpers@6.0.2", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="], + + "@csstools/css-calc": ["@csstools/css-calc@3.2.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg=="], + + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.1", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.4", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="], + + "@farish/api": ["@farish/api@workspace:services/api"], + + "@farish/api-contract": ["@farish/api-contract@workspace:lib/api-contract"], + + "@farish/cli": ["@farish/cli@workspace:apps/cli"], + + "@farish/core": ["@farish/core@workspace:lib/core"], + + "@farish/echo-service": ["@farish/echo-service@workspace:services/echo-service"], + + "@farish/math": ["@farish/math@workspace:lib/math"], + + "@farish/mock-data": ["@farish/mock-data@workspace:lib/mock-data"], + + "@farish/sdk": ["@farish/sdk@workspace:packages/sdk"], + + "@farish/web": ["@farish/web@workspace:apps/web"], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@jest/diff-sequences": ["@jest/diff-sequences@30.4.0", "", {}, "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g=="], + + "@jest/get-type": ["@jest/get-type@30.1.0", "", {}, "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA=="], + + "@jest/schemas": ["@jest/schemas@30.4.1", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mdi/font": ["@mdi/font@7.4.47", "", {}, "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.4", "", { "dependencies": { "@emnapi/core": "^1.1.0", "@emnapi/runtime": "^1.1.0", "@tybys/wasm-util": "^0.9.0" } }, "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ=="], + + "@nx/nx-darwin-arm64": ["@nx/nx-darwin-arm64@21.6.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4hXhV7ShXIlfPEjjm7dJY383xM2vTcnkKr5FUncAU08GKkkL67ib5CMlQADtdi32ewfCZntqiT8gUfFFSNvKtA=="], + + "@nx/nx-darwin-x64": ["@nx/nx-darwin-x64@21.6.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-VxjKkzyhdO47X7d4JPx/f6HslERKetFmouDUBIoqbKDPVWpRegGMsRKcMYh4l61usxI1Qa2U4Ec6MgO5Fnm41g=="], + + "@nx/nx-freebsd-x64": ["@nx/nx-freebsd-x64@21.6.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-3jDn7Tb3FMFfeFTM/XKAcI+J92kDLNmxUS/N/n/+kF/XYLPgMUZQqSeDpVifk4fgy0BCoH8DSMQIqTQau6dV/g=="], + + "@nx/nx-linux-arm-gnueabihf": ["@nx/nx-linux-arm-gnueabihf@21.6.11", "", { "os": "linux", "cpu": "arm" }, "sha512-37tpiVod5FN/EAuCGh+uad/6nsfDFze02OjYReUPKeYPs8Q7Ac/V9j4kn/y5uZhKSle+9+sa2QR/K79B0+lNxw=="], + + "@nx/nx-linux-arm64-gnu": ["@nx/nx-linux-arm64-gnu@21.6.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-r+czH0OtldQqFm2B6BBBUPW8aO+sBMvpZCgN855vko30WaCeXo8Fkuk71rQMxByz0jnoCxSnzLtEWVZm1rmJYA=="], + + "@nx/nx-linux-arm64-musl": ["@nx/nx-linux-arm64-musl@21.6.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-0FAPyWEGPCukXxR1qowvFx6Q/cU906vwPlAQtcbrBU1e2H2aMUslk9EmL8iHb5MsHdmGcFyFemwr9oN8gxmz4g=="], + + "@nx/nx-linux-x64-gnu": ["@nx/nx-linux-x64-gnu@21.6.11", "", { "os": "linux", "cpu": "x64" }, "sha512-+bWJWXJ8tdddl1L3bTKE0VmvTmdsk4zzUr6P9ts9hXQbwoWqMcuA6LNqOhUfzVOL3VioirRMtKMfFuUAUhi3Yg=="], + + "@nx/nx-linux-x64-musl": ["@nx/nx-linux-x64-musl@21.6.11", "", { "os": "linux", "cpu": "x64" }, "sha512-Mh09mLc+yeJk7DKfx7x6XfB+bm2dP1/7gyUuRQj4WjkT+Il2ZponyTuH8LmX06Jpr7efAAFk+8lBXeleV7XvMw=="], + + "@nx/nx-win32-arm64-msvc": ["@nx/nx-win32-arm64-msvc@21.6.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-d7ZeCCDwaeyKiWD2JLSSxMSuSHHwIdpbnMtZtzRE6tDdAgs6e9F36/OBNQB3IRXH8V6QKOy2OGDyWeOKr01X9A=="], + + "@nx/nx-win32-x64-msvc": ["@nx/nx-win32-x64-msvc@21.6.11", "", { "os": "win32", "cpu": "x64" }, "sha512-otHSkhyoGilttV4RRkVmLLGb2W+Ia4b90lWxLnEm9jAAKmA9O2FUG2vAvKDHNcqTyDwwCcHfHxslgnR1u/3OIg=="], + + "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], + + "@oxc-project/types": ["@oxc-project/types@0.130.0", "", {}, "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@playwright/test": ["@playwright/test@1.50.1", "", { "dependencies": { "playwright": "1.50.1" }, "bin": { "playwright": "cli.js" } }, "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.1", "", { "os": "android", "cpu": "arm64" }, "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.1", "", { "os": "none", "cpu": "arm64" }, "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.1", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.7", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg=="], + + "@vitest/expect": ["@vitest/expect@4.1.7", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.7", "", { "dependencies": { "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.7", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw=="], + + "@vitest/runner": ["@vitest/runner@4.1.7", "", { "dependencies": { "@vitest/utils": "4.1.7", "pathe": "^2.0.3" } }, "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw=="], + + "@vitest/spy": ["@vitest/spy@4.1.7", "", {}, "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q=="], + + "@vitest/utils": ["@vitest/utils@4.1.7", "", { "dependencies": { "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw=="], + + "@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.34", "", { "dependencies": { "@babel/parser": "^7.29.3", "@vue/shared": "3.5.34", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.34", "", { "dependencies": { "@vue/compiler-core": "3.5.34", "@vue/shared": "3.5.34" } }, "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.34", "", { "dependencies": { "@babel/parser": "^7.29.3", "@vue/compiler-core": "3.5.34", "@vue/compiler-dom": "3.5.34", "@vue/compiler-ssr": "3.5.34", "@vue/shared": "3.5.34", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.14", "source-map-js": "^1.2.1" } }, "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.34", "", { "dependencies": { "@vue/compiler-dom": "3.5.34", "@vue/shared": "3.5.34" } }, "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ=="], + + "@vue/devtools-api": ["@vue/devtools-api@8.1.2", "", { "dependencies": { "@vue/devtools-kit": "^8.1.2" } }, "sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.1.2", "", { "dependencies": { "@vue/devtools-shared": "^8.1.2", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.1.2", "", {}, "sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.34", "", { "dependencies": { "@vue/shared": "3.5.34" } }, "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.34", "", { "dependencies": { "@vue/reactivity": "3.5.34", "@vue/shared": "3.5.34" } }, "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.34", "", { "dependencies": { "@vue/reactivity": "3.5.34", "@vue/runtime-core": "3.5.34", "@vue/shared": "3.5.34", "csstype": "^3.2.3" } }, "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.34", "", { "dependencies": { "@vue/compiler-ssr": "3.5.34", "@vue/shared": "3.5.34" }, "peerDependencies": { "vue": "3.5.34" } }, "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew=="], + + "@vue/shared": ["@vue/shared@3.5.34", "", {}, "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA=="], + + "@vue/test-utils": ["@vue/test-utils@2.4.10", "", { "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^3.0.0" }, "peerDependencies": { "@vue/compiler-dom": "3.x", "@vue/server-renderer": "3.x", "vue": "3.x" }, "optionalPeers": ["@vue/server-renderer"] }, "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA=="], + + "@vuetify/loader-shared": ["@vuetify/loader-shared@2.1.2", "", { "dependencies": { "upath": "^2.0.1" }, "peerDependencies": { "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig=="], + + "@yarnpkg/lockfile": ["@yarnpkg/lockfile@1.1.0", "", {}, "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="], + + "@yarnpkg/parsers": ["@yarnpkg/parsers@3.0.2", "", { "dependencies": { "js-yaml": "^3.10.0", "tslib": "^2.4.0" } }, "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA=="], + + "@zkochan/js-yaml": ["@zkochan/js-yaml@0.0.7", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ=="], + + "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + + "ast-walker-scope": ["ast-walker-scope@0.8.3", "", { "dependencies": { "@babel/parser": "^7.28.4", "ast-kit": "^2.1.3" } }, "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + + "cli-spinners": ["cli-spinners@2.6.1", "", {}, "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], + + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "editorconfig": ["editorconfig@1.0.7", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enquirer": ["enquirer@2.3.6", "", { "dependencies": { "ansi-colors": "^4.1.1" } }, "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg=="], + + "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="], + + "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "front-matter": ["front-matter@4.0.2", "", { "dependencies": { "js-yaml": "^3.13.1" } }, "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jest-diff": ["jest-diff@30.4.1", "", { "dependencies": { "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "pretty-format": "30.4.1" } }, "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA=="], + + "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], + + "js-cookie": ["js-cookie@3.0.7", "", {}, "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw=="], + + "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "jsdom": ["jsdom@29.1.1", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.3.5", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.2.0", "", {}, "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@2.0.3", "", {}, "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w=="], + + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + + "lru-cache": ["lru-cache@11.5.0", "", {}, "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + + "marked": ["marked@15.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "node-machine-id": ["node-machine-id@1.1.12", "", {}, "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ=="], + + "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "nx": ["nx@21.6.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", "axios": "^1.12.0", "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", "cliui": "^8.0.1", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "enquirer": "~2.3.6", "figures": "3.2.0", "flat": "^5.0.2", "front-matter": "^4.0.2", "ignore": "^5.0.4", "jest-diff": "^30.0.2", "jsonc-parser": "3.2.0", "lines-and-columns": "2.0.3", "minimatch": "9.0.3", "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", "open": "^8.4.0", "ora": "5.3.0", "resolve.exports": "2.0.3", "semver": "^7.5.3", "string-width": "^4.2.3", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tree-kill": "^1.2.2", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", "yaml": "^2.6.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, "optionalDependencies": { "@nx/nx-darwin-arm64": "21.6.11", "@nx/nx-darwin-x64": "21.6.11", "@nx/nx-freebsd-x64": "21.6.11", "@nx/nx-linux-arm-gnueabihf": "21.6.11", "@nx/nx-linux-arm64-gnu": "21.6.11", "@nx/nx-linux-arm64-musl": "21.6.11", "@nx/nx-linux-x64-gnu": "21.6.11", "@nx/nx-linux-x64-musl": "21.6.11", "@nx/nx-win32-arm64-msvc": "21.6.11", "@nx/nx-win32-x64-msvc": "21.6.11" }, "peerDependencies": { "@swc-node/register": "^1.8.0", "@swc/core": "^1.3.85" }, "optionalPeers": ["@swc-node/register", "@swc/core"], "bin": { "nx": "bin/nx.js", "nx-cloud": "bin/nx-cloud.js" } }, "sha512-AAgJGhS+7xlsmZF6ArKX1vgONxf7IymUYZ1BxGXHVa5927rGfgKoMaPOgwwtvN0OL3o/QYaNGwlDfIzCvlpOLQ=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "ora": ["ora@5.3.0", "", { "dependencies": { "bl": "^4.0.3", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "log-symbols": "^4.0.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + + "playwright": ["playwright@1.50.1", "", { "dependencies": { "playwright-core": "1.50.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw=="], + + "playwright-core": ["playwright-core@1.50.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "pretty-format": ["pretty-format@30.4.1", "", { "dependencies": { "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", "react-is-18": "npm:react-is@^18.3.1", "react-is-19": "npm:react-is@^19.2.5" } }, "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw=="], + + "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "react-is-18": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "react-is-19": ["react-is@19.2.6", "", {}, "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], + + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + + "rolldown": ["rolldown@1.0.1", "", { "dependencies": { "@oxc-project/types": "=0.130.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.1", "@rolldown/binding-darwin-arm64": "1.0.1", "@rolldown/binding-darwin-x64": "1.0.1", "@rolldown/binding-freebsd-x64": "1.0.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", "@rolldown/binding-linux-arm64-gnu": "1.0.1", "@rolldown/binding-linux-arm64-musl": "1.0.1", "@rolldown/binding-linux-ppc64-gnu": "1.0.1", "@rolldown/binding-linux-s390x-gnu": "1.0.1", "@rolldown/binding-linux-x64-gnu": "1.0.1", "@rolldown/binding-linux-x64-musl": "1.0.1", "@rolldown/binding-openharmony-arm64": "1.0.1", "@rolldown/binding-wasm32-wasi": "1.0.1", "@rolldown/binding-win32-arm64-msvc": "1.0.1", "@rolldown/binding-win32-x64-msvc": "1.0.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], + + "tldts-core": ["tldts-core@7.0.30", "", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], + + "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], + + "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], + + "upath": ["upath@2.0.1", "", {}, "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vite": ["vite@8.0.13", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.1", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw=="], + + "vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.3", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.2", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": ">=3" } }, "sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw=="], + + "vitest": ["vitest@4.1.7", "", { "dependencies": { "@vitest/expect": "4.1.7", "@vitest/mocker": "4.1.7", "@vitest/pretty-format": "4.1.7", "@vitest/runner": "4.1.7", "@vitest/snapshot": "4.1.7", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.7", "@vitest/browser-preview": "4.1.7", "@vitest/browser-webdriverio": "4.1.7", "@vitest/coverage-istanbul": "4.1.7", "@vitest/coverage-v8": "4.1.7", "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA=="], + + "vue": ["vue@3.5.34", "", { "dependencies": { "@vue/compiler-dom": "3.5.34", "@vue/compiler-sfc": "3.5.34", "@vue/runtime-dom": "3.5.34", "@vue/server-renderer": "3.5.34", "@vue/shared": "3.5.34" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA=="], + + "vue-component-type-helpers": ["vue-component-type-helpers@3.3.1", "", {}, "sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA=="], + + "vue-router": ["vue-router@5.0.7", "", { "dependencies": { "@babel/generator": "^8.0.0-rc.4", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.1.1", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34", "pinia": "^3.0.4", "vue": "^3.5.34" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-dqfk8kvRbCutmCOCj/XLDqDEYxc1wBdAOGLuVy5M93ifYMsBd5fIjfaPN4tQAbxr5IprdBDIox1gr4wYyOx/SA=="], + + "vuetify": ["vuetify@4.0.7", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-SV+YJkBmudY3s9qfZO2ZGUsrD0TDQU8pMBH1ERga9AEyjGvioZuXXh93V9wHxXJphvqRC2NW10Nt2lW9IZQcPw=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + + "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], + + "whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "@babel/generator/@babel/parser": ["@babel/parser@8.0.0-rc.5", "", { "dependencies": { "@babel/types": "^8.0.0-rc.5" }, "bin": "./bin/babel-parser.js" }, "sha512-/Mfg83rK3+jsRbl4Vbd0jqxc6M1A1/WNFtgrowRM1unEsD3XcNnrBdMM0JWakd0/RN9lseQKwPduW1TiEwKOlQ=="], + + "@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + } +} diff --git a/ci-debug/20260521T145941Z-26234087189/MANIFEST.txt b/ci-debug/20260521T145941Z-26234087189/MANIFEST.txt new file mode 100644 index 0000000..ef18be1 --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T145941Z-26234087189 +github.ref: refs/pull/1/merge +github.sha: 527c3bd463ffc708f9b25537358264a48d9892a2 +github.run_id: 26234087189 +github.run_number:6 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T145941Z-26234087189/MANIFEST.txt +ci-debug/20260521T145941Z-26234087189/chromium-probe.txt +ci-debug/20260521T145941Z-26234087189/e2e-repro.txt +ci-debug/20260521T145941Z-26234087189/environment.txt +ci-debug/20260521T145941Z-26234087189/playwright-install.txt +ci-debug/20260521T145941Z-26234087189/vite-build.txt diff --git a/ci-debug/20260521T145941Z-26234087189/chromium-probe.txt b/ci-debug/20260521T145941Z-26234087189/chromium-probe.txt new file mode 100644 index 0000000..f633e9d --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3748:3764:0521/145944.764579:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.764634:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.764642:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.764653:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.764668:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3764:0521/145944.791630:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.804991:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3764:0521/145944.805018:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3748:0521/145944.879316:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3764:0521/145944.879399:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3829:0521/145944.891665:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3748:3829:0521/145944.891881:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3748:3829:0521/145944.892103:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3748:3748:0521/145944.895897:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3761:0521/145944.945353:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3748:0521/145944.958882:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3761:0521/145944.959615:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3748:0521/145944.962121:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3761:0521/145944.962343:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3748:0521/145944.963289:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3761:0521/145944.963322:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3748:0521/145944.969259:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3748:3761:0521/145944.969314:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3748:3748:0521/145944.972556:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T145941Z-26234087189/e2e-repro.txt b/ci-debug/20260521T145941Z-26234087189/e2e-repro.txt new file mode 100644 index 0000000..82cf143 --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 250 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-CJN_hhmM.css 621.60 kB │ gzip: 95.71 kB +dist/assets/index-ppPwkYtX.js 289.60 kB │ gzip: 100.54 kB + +✗ Build failed in 560ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/api-contract" from "/home/runner/work/farish/farish/apps/web/src/api/client.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T145941Z-26234087189/environment.txt b/ci-debug/20260521T145941Z-26234087189/environment.txt new file mode 100644 index 0000000..48bc019 --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmrw5os 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 145G 57G 88G 40% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.3Gi 10Gi 47Mi 3.9Gi 14Gi +Swap: 3.0Gi 0B 3.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=e3a3010e-43b3-46d2-abb1-31532500f878.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_de6fc7a9-2e77-4918-a578-446d55352cdd +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_de6fc7a9-2e77-4918-a578-446d55352cdd +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_de6fc7a9-2e77-4918-a578-446d55352cdd +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26234087189 +GITHUB_RUN_NUMBER=6 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=527c3bd463ffc708f9b25537358264a48d9892a2 +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_de6fc7a9-2e77-4918-a578-446d55352cdd +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_de6fc7a9-2e77-4918-a578-446d55352cdd +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=527c3bd463ffc708f9b25537358264a48d9892a2 +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=ef21b1ea47dd49cb95346ae1034ec0fe +ImageOS=ubuntu24 +ImageVersion=20260513.135.3 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:13231 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074326 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_7a11e64c-3633-41ae-98d9-3d1cfe44ca0e +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2100 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T145941Z-26234087189/playwright-install.txt b/ci-debug/20260521T145941Z-26234087189/playwright-install.txt new file mode 100644 index 0000000..512cf1f --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 10 not upgraded. diff --git a/ci-debug/20260521T145941Z-26234087189/vite-build.txt b/ci-debug/20260521T145941Z-26234087189/vite-build.txt new file mode 100644 index 0000000..baa7967 --- /dev/null +++ b/ci-debug/20260521T145941Z-26234087189/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 250 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-CJN_hhmM.css 621.60 kB │ gzip: 95.71 kB +dist/assets/index-ppPwkYtX.js 289.60 kB │ gzip: 100.54 kB + +✗ Build failed in 559ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/api-contract" from "/home/runner/work/farish/farish/apps/web/src/api/client.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T150139Z-26234208053/MANIFEST.txt b/ci-debug/20260521T150139Z-26234208053/MANIFEST.txt new file mode 100644 index 0000000..f2647f0 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T150139Z-26234208053 +github.ref: refs/pull/1/merge +github.sha: 85ea96734d313790e006ae13a265d5387ec22554 +github.run_id: 26234208053 +github.run_number:7 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T150139Z-26234208053/MANIFEST.txt +ci-debug/20260521T150139Z-26234208053/chromium-probe.txt +ci-debug/20260521T150139Z-26234208053/e2e-repro.txt +ci-debug/20260521T150139Z-26234208053/environment.txt +ci-debug/20260521T150139Z-26234208053/playwright-install.txt +ci-debug/20260521T150139Z-26234208053/vite-build.txt diff --git a/ci-debug/20260521T150139Z-26234208053/chromium-probe.txt b/ci-debug/20260521T150139Z-26234208053/chromium-probe.txt new file mode 100644 index 0000000..becb138 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3736:3752:0521/150143.475530:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.475749:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.475758:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.475765:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.475780:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3752:0521/150143.486286:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.503551:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3752:0521/150143.503571:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3736:0521/150143.558443:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3752:0521/150143.558557:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3818:0521/150143.569014:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3736:3818:0521/150143.569253:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3736:3818:0521/150143.569482:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3736:3736:0521/150143.571301:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3750:0521/150143.626103:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3736:0521/150143.641084:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3748:0521/150143.641857:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3736:0521/150143.644368:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3749:0521/150143.644446:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3736:0521/150143.652375:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3748:0521/150143.652444:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3736:0521/150143.653570:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3736:3748:0521/150143.653629:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3736:3736:0521/150143.659185:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T150139Z-26234208053/e2e-repro.txt b/ci-debug/20260521T150139Z-26234208053/e2e-repro.txt new file mode 100644 index 0000000..1ae12e5 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 619ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T150139Z-26234208053/environment.txt b/ci-debug/20260521T150139Z-26234208053/environment.txt new file mode 100644 index 0000000..d1dfdd7 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmrw5os 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 145G 57G 88G 40% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.5Gi 10Gi 48Mi 3.9Gi 14Gi +Swap: 3.0Gi 0B 3.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=fccb8d6d-845d-4594-aa4a-3c525746e786.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_c3c3a8fd-f3ff-4439-ac28-c5f4d6e50044 +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_c3c3a8fd-f3ff-4439-ac28-c5f4d6e50044 +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_c3c3a8fd-f3ff-4439-ac28-c5f4d6e50044 +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26234208053 +GITHUB_RUN_NUMBER=7 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=85ea96734d313790e006ae13a265d5387ec22554 +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_c3c3a8fd-f3ff-4439-ac28-c5f4d6e50044 +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_c3c3a8fd-f3ff-4439-ac28-c5f4d6e50044 +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=85ea96734d313790e006ae13a265d5387ec22554 +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=e990f3732e4d4dd58b911d89ee6ba728 +ImageOS=ubuntu24 +ImageVersion=20260513.135.3 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:16511 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074328 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_12fea5e3-ee11-41fe-8da3-6190936d5c69 +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2091 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T150139Z-26234208053/playwright-install.txt b/ci-debug/20260521T150139Z-26234208053/playwright-install.txt new file mode 100644 index 0000000..36e6602 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 10 not upgraded. diff --git a/ci-debug/20260521T150139Z-26234208053/vite-build.txt b/ci-debug/20260521T150139Z-26234208053/vite-build.txt new file mode 100644 index 0000000..6073731 --- /dev/null +++ b/ci-debug/20260521T150139Z-26234208053/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 617ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T150237Z-26234271459/MANIFEST.txt b/ci-debug/20260521T150237Z-26234271459/MANIFEST.txt new file mode 100644 index 0000000..a753a6e --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T150237Z-26234271459 +github.ref: refs/pull/1/merge +github.sha: f57e119f22c63d3003a86d4445186bfdec4de49d +github.run_id: 26234271459 +github.run_number:8 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T150237Z-26234271459/MANIFEST.txt +ci-debug/20260521T150237Z-26234271459/chromium-probe.txt +ci-debug/20260521T150237Z-26234271459/e2e-repro.txt +ci-debug/20260521T150237Z-26234271459/environment.txt +ci-debug/20260521T150237Z-26234271459/playwright-install.txt +ci-debug/20260521T150237Z-26234271459/vite-build.txt diff --git a/ci-debug/20260521T150237Z-26234271459/chromium-probe.txt b/ci-debug/20260521T150237Z-26234271459/chromium-probe.txt new file mode 100644 index 0000000..2ee523f --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3686:3702:0521/150241.801700:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.801766:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.801775:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.801782:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.801802:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3702:0521/150241.818537:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.836010:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3702:0521/150241.836029:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3686:0521/150241.889426:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3702:0521/150241.890813:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3767:0521/150241.900710:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3686:3767:0521/150241.902166:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3686:3767:0521/150241.902399:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3686:3686:0521/150241.906872:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3699:0521/150241.952401:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3686:0521/150241.965730:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3698:0521/150241.965808:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3686:0521/150241.971233:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3698:0521/150241.971299:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3686:0521/150241.972699:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3699:0521/150241.972768:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3686:0521/150241.978735:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3686:3698:0521/150241.978795:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3686:3686:0521/150241.979250:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T150237Z-26234271459/e2e-repro.txt b/ci-debug/20260521T150237Z-26234271459/e2e-repro.txt new file mode 100644 index 0000000..d870167 --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 627ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T150237Z-26234271459/environment.txt b/ci-debug/20260521T150237Z-26234271459/environment.txt new file mode 100644 index 0000000..de389cc --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmg397c 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 72G 54G 18G 75% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.6Gi 10Gi 48Mi 4.0Gi 13Gi +Swap: 4.0Gi 0B 4.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=add8ddeb-5947-4dc2-b357-882a992d2773.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_37300b96-e74d-4c44-aee7-5b8907931ba1 +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_37300b96-e74d-4c44-aee7-5b8907931ba1 +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_37300b96-e74d-4c44-aee7-5b8907931ba1 +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26234271459 +GITHUB_RUN_NUMBER=8 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=f57e119f22c63d3003a86d4445186bfdec4de49d +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_37300b96-e74d-4c44-aee7-5b8907931ba1 +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_37300b96-e74d-4c44-aee7-5b8907931ba1 +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=f57e119f22c63d3003a86d4445186bfdec4de49d +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=b974ab18de7c45898fbe6ee78f55f073 +ImageOS=ubuntu24 +ImageVersion=20260518.149.1 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:14097 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074330 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_548eb079-69d0-4d89-af94-99bbc83b3879 +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2042 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T150237Z-26234271459/playwright-install.txt b/ci-debug/20260521T150237Z-26234271459/playwright-install.txt new file mode 100644 index 0000000..29f9bbe --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 5 not upgraded. diff --git a/ci-debug/20260521T150237Z-26234271459/vite-build.txt b/ci-debug/20260521T150237Z-26234271459/vite-build.txt new file mode 100644 index 0000000..4b0f251 --- /dev/null +++ b/ci-debug/20260521T150237Z-26234271459/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 635ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T151009Z-26234598336/MANIFEST.txt b/ci-debug/20260521T151009Z-26234598336/MANIFEST.txt new file mode 100644 index 0000000..e03cecd --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T151009Z-26234598336 +github.ref: refs/pull/1/merge +github.sha: 5aa348c8d92c8525df7ac03a07c8f4adb699a856 +github.run_id: 26234598336 +github.run_number:9 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T151009Z-26234598336/MANIFEST.txt +ci-debug/20260521T151009Z-26234598336/chromium-probe.txt +ci-debug/20260521T151009Z-26234598336/e2e-repro.txt +ci-debug/20260521T151009Z-26234598336/environment.txt +ci-debug/20260521T151009Z-26234598336/playwright-install.txt +ci-debug/20260521T151009Z-26234598336/vite-build.txt diff --git a/ci-debug/20260521T151009Z-26234598336/chromium-probe.txt b/ci-debug/20260521T151009Z-26234598336/chromium-probe.txt new file mode 100644 index 0000000..47394c1 --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3725:3741:0521/151013.308733:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.308808:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.308816:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.308890:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.308907:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3741:0521/151013.320092:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.335339:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3741:0521/151013.335393:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3725:0521/151013.389182:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3741:0521/151013.390488:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3808:0521/151013.402727:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3725:3808:0521/151013.402953:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3725:3808:0521/151013.403231:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3725:3725:0521/151013.404723:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3738:0521/151013.451902:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3725:0521/151013.466825:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3739:0521/151013.467376:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3725:0521/151013.470413:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3739:0521/151013.470658:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3725:0521/151013.480209:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3739:0521/151013.480276:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3725:0521/151013.480818:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3725:3739:0521/151013.480874:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3725:3725:0521/151013.486816:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T151009Z-26234598336/e2e-repro.txt b/ci-debug/20260521T151009Z-26234598336/e2e-repro.txt new file mode 100644 index 0000000..054ea14 --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 614ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T151009Z-26234598336/environment.txt b/ci-debug/20260521T151009Z-26234598336/environment.txt new file mode 100644 index 0000000..accf815 --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmrw5os 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 145G 57G 88G 40% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.2Gi 10Gi 49Mi 3.9Gi 14Gi +Swap: 3.0Gi 0B 3.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=b85bdb19-0712-4169-ab40-e379841d98ce.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_5f5aa40f-6988-4e0d-9632-fdfabc3834a0 +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_5f5aa40f-6988-4e0d-9632-fdfabc3834a0 +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_5f5aa40f-6988-4e0d-9632-fdfabc3834a0 +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26234598336 +GITHUB_RUN_NUMBER=9 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=5aa348c8d92c8525df7ac03a07c8f4adb699a856 +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_5f5aa40f-6988-4e0d-9632-fdfabc3834a0 +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_5f5aa40f-6988-4e0d-9632-fdfabc3834a0 +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=5aa348c8d92c8525df7ac03a07c8f4adb699a856 +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=4b736646e59f486f9b2af5c30660cbb2 +ImageOS=ubuntu24 +ImageVersion=20260513.135.3 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:12288 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074333 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_64940519-2b75-445b-970f-7c124d04df29 +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2019 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T151009Z-26234598336/playwright-install.txt b/ci-debug/20260521T151009Z-26234598336/playwright-install.txt new file mode 100644 index 0000000..36e6602 --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 10 not upgraded. diff --git a/ci-debug/20260521T151009Z-26234598336/vite-build.txt b/ci-debug/20260521T151009Z-26234598336/vite-build.txt new file mode 100644 index 0000000..3b15308 --- /dev/null +++ b/ci-debug/20260521T151009Z-26234598336/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 320 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.28 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-AZhz1pWj.css 652.64 kB │ gzip: 99.76 kB +dist/assets/index-kT8BjdUg.js 362.10 kB │ gzip: 122.17 kB + +✗ Build failed in 638ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/components/GhostModelGrid.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T165633Z-26240484976/MANIFEST.txt b/ci-debug/20260521T165633Z-26240484976/MANIFEST.txt new file mode 100644 index 0000000..e027e1a --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T165633Z-26240484976 +github.ref: refs/pull/1/merge +github.sha: d0350800dd2a9c5d835cc7db5a209cdf24d42e1c +github.run_id: 26240484976 +github.run_number:11 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T165633Z-26240484976/MANIFEST.txt +ci-debug/20260521T165633Z-26240484976/chromium-probe.txt +ci-debug/20260521T165633Z-26240484976/e2e-repro.txt +ci-debug/20260521T165633Z-26240484976/environment.txt +ci-debug/20260521T165633Z-26240484976/playwright-install.txt +ci-debug/20260521T165633Z-26240484976/vite-build.txt diff --git a/ci-debug/20260521T165633Z-26240484976/chromium-probe.txt b/ci-debug/20260521T165633Z-26240484976/chromium-probe.txt new file mode 100644 index 0000000..355a88e --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3701:3717:0521/165637.972828:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.972895:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.972903:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.972943:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.972980:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3717:0521/165637.985251:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.998187:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3717:0521/165637.998214:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3701:0521/165638.055210:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3717:0521/165638.057524:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3781:0521/165638.064587:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3701:3781:0521/165638.064812:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3701:3781:0521/165638.065016:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3701:3701:0521/165638.070170:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3714:0521/165638.129415:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3701:0521/165638.144105:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3714:0521/165638.145233:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3701:0521/165638.149447:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3713:0521/165638.150462:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3701:0521/165638.151789:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3713:0521/165638.151869:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3701:0521/165638.158570:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3701:3713:0521/165638.159730:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3701:3701:0521/165638.159967:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T165633Z-26240484976/e2e-repro.txt b/ci-debug/20260521T165633Z-26240484976/e2e-repro.txt new file mode 100644 index 0000000..328c943 --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 342 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-znNMzPxn.js 398.05 kB │ gzip: 132.38 kB + +✗ Build failed in 735ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/views/HomeView.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T165633Z-26240484976/environment.txt b/ci-debug/20260521T165633Z-26240484976/environment.txt new file mode 100644 index 0000000..412fa7a --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmrw5os 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 145G 57G 88G 40% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.5Gi 10Gi 48Mi 3.9Gi 14Gi +Swap: 3.0Gi 0B 3.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=939855a2-c75b-4b1c-8031-a6b73a93aba5.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_57e68959-f18f-4fc6-a37d-60d6fc442723 +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_57e68959-f18f-4fc6-a37d-60d6fc442723 +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_57e68959-f18f-4fc6-a37d-60d6fc442723 +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26240484976 +GITHUB_RUN_NUMBER=11 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=d0350800dd2a9c5d835cc7db5a209cdf24d42e1c +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_57e68959-f18f-4fc6-a37d-60d6fc442723 +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_57e68959-f18f-4fc6-a37d-60d6fc442723 +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=d0350800dd2a9c5d835cc7db5a209cdf24d42e1c +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=c0716ffb4a8145ac8a11ccaed28470b7 +ImageOS=ubuntu24 +ImageVersion=20260513.135.3 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:14638 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074348 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_24c12443-3a0b-4f34-994a-a28bda55debd +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2088 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T165633Z-26240484976/playwright-install.txt b/ci-debug/20260521T165633Z-26240484976/playwright-install.txt new file mode 100644 index 0000000..787a501 --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:7 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:8 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded. diff --git a/ci-debug/20260521T165633Z-26240484976/vite-build.txt b/ci-debug/20260521T165633Z-26240484976/vite-build.txt new file mode 100644 index 0000000..5f07778 --- /dev/null +++ b/ci-debug/20260521T165633Z-26240484976/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 342 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-znNMzPxn.js 398.05 kB │ gzip: 132.38 kB + +✗ Build failed in 750ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/views/HomeView.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T165936Z-26240649335/MANIFEST.txt b/ci-debug/20260521T165936Z-26240649335/MANIFEST.txt new file mode 100644 index 0000000..6e851fb --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T165936Z-26240649335 +github.ref: refs/pull/1/merge +github.sha: 55fa27511477a6a0cb1b12f8616d9e247ab7b05d +github.run_id: 26240649335 +github.run_number:12 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T165936Z-26240649335/MANIFEST.txt +ci-debug/20260521T165936Z-26240649335/chromium-probe.txt +ci-debug/20260521T165936Z-26240649335/e2e-repro.txt +ci-debug/20260521T165936Z-26240649335/environment.txt +ci-debug/20260521T165936Z-26240649335/playwright-install.txt +ci-debug/20260521T165936Z-26240649335/vite-build.txt diff --git a/ci-debug/20260521T165936Z-26240649335/chromium-probe.txt b/ci-debug/20260521T165936Z-26240649335/chromium-probe.txt new file mode 100644 index 0000000..f56a051 --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3714:3731:0521/165940.472880:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.472956:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.472965:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.472974:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.472991:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3731:0521/165940.498239:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.513203:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3731:0521/165940.513243:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3714:0521/165940.590732:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3731:0521/165940.590844:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3796:0521/165940.608911:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3714:3796:0521/165940.609145:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3714:3796:0521/165940.609461:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3714:3714:0521/165940.612116:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3727:0521/165940.658806:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3714:0521/165940.672210:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3726:0521/165940.672648:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3714:0521/165940.678887:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3728:0521/165940.679568:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3714:0521/165940.687071:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3726:0521/165940.687142:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3714:0521/165940.687541:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3714:3726:0521/165940.687596:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3714:3714:0521/165940.687815:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T165936Z-26240649335/e2e-repro.txt b/ci-debug/20260521T165936Z-26240649335/e2e-repro.txt new file mode 100644 index 0000000..a8bb857 --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/e2e-repro.txt @@ -0,0 +1,41 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 342 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-znNMzPxn.js 398.05 kB │ gzip: 132.38 kB + +✗ Build failed in 677ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/views/MyLibraryView.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "e2e" exited with code 1 diff --git a/ci-debug/20260521T165936Z-26240649335/environment.txt b/ci-debug/20260521T165936Z-26240649335/environment.txt new file mode 100644 index 0000000..0de0b2b --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmg397c 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 145G 57G 88G 40% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.3Gi 10Gi 48Mi 3.9Gi 14Gi +Swap: 3.0Gi 0B 3.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=d44e418e-bef5-44f8-9dae-3326ba624421.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_071b2a9d-9afd-4067-b558-b36abe74f23d +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_071b2a9d-9afd-4067-b558-b36abe74f23d +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_071b2a9d-9afd-4067-b558-b36abe74f23d +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26240649335 +GITHUB_RUN_NUMBER=12 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=55fa27511477a6a0cb1b12f8616d9e247ab7b05d +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_071b2a9d-9afd-4067-b558-b36abe74f23d +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_071b2a9d-9afd-4067-b558-b36abe74f23d +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=55fa27511477a6a0cb1b12f8616d9e247ab7b05d +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=89f3cfd3d65d4e99831764dc74b00df7 +ImageOS=ubuntu24 +ImageVersion=20260518.149.1 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:11521 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074351 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_f982bc2f-8398-449f-9e0f-9e80976541c3 +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2100 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T165936Z-26240649335/playwright-install.txt b/ci-debug/20260521T165936Z-26240649335/playwright-install.txt new file mode 100644 index 0000000..cbd2654 --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 5 not upgraded. diff --git a/ci-debug/20260521T165936Z-26240649335/vite-build.txt b/ci-debug/20260521T165936Z-26240649335/vite-build.txt new file mode 100644 index 0000000..2a94c25 --- /dev/null +++ b/ci-debug/20260521T165936Z-26240649335/vite-build.txt @@ -0,0 +1,37 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 342 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-znNMzPxn.js 398.05 kB │ gzip: 132.38 kB + +✗ Build failed in 708ms +error during build: +Build failed with 1 error: + +Error: [vite]: Rolldown failed to resolve import "@farish/mock-data" from "/home/runner/work/farish/farish/apps/web/src/views/HomeView.vue?vue&type=script&setup=true&lang.ts". +This is most likely unintended because it can break your application at runtime. +If you do want to externalize this module explicitly add it to +`build.rolldownOptions.external` + at viteLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33226:57) + at onRollupLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33258:7) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33026:4) + at file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:50:26 + at logger (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:41:3) + at onLog (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/bindingify-input-options-BnfFBV6b.mjs:2013:32) + at aggregateBindingErrorsIntoJsError (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18) + at unwrapBindingResult (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128) + at #build (file:///home/runner/work/farish/farish/node_modules/.bun/rolldown@1.0.1/node_modules/rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34) + at async buildEnvironment (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33133:64) + at async Object.build (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33555:19) + at async Object.buildApp (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/chunks/node.js:33552:153) + at async CAC. (file:///home/runner/work/farish/farish/node_modules/.bun/vite@8.0.13+3b83efd5160cf92a/node_modules/vite/dist/node/cli.js:777:3) { + errors: [Getter/Setter] +} +error: script "build" exited with code 1 diff --git a/ci-debug/20260521T170427Z-26240904544/MANIFEST.txt b/ci-debug/20260521T170427Z-26240904544/MANIFEST.txt new file mode 100644 index 0000000..e7b6d27 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/MANIFEST.txt @@ -0,0 +1,14 @@ +ci-debug collection: 20260521T170427Z-26240904544 +github.ref: refs/pull/1/merge +github.sha: 4b61ee5d34733aa48d7a03a04c826263df7fa7ee +github.run_id: 26240904544 +github.run_number:13 +github.actor: nsheaps + +=== Files collected === +ci-debug/20260521T170427Z-26240904544/MANIFEST.txt +ci-debug/20260521T170427Z-26240904544/chromium-probe.txt +ci-debug/20260521T170427Z-26240904544/e2e-repro.txt +ci-debug/20260521T170427Z-26240904544/environment.txt +ci-debug/20260521T170427Z-26240904544/playwright-install.txt +ci-debug/20260521T170427Z-26240904544/vite-build.txt diff --git a/ci-debug/20260521T170427Z-26240904544/chromium-probe.txt b/ci-debug/20260521T170427Z-26240904544/chromium-probe.txt new file mode 100644 index 0000000..4509561 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/chromium-probe.txt @@ -0,0 +1,34 @@ +=== Chromium binary location === +/home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome + +=== Chromium executable probe === +Found: /home/runner/.cache/ms-playwright/chromium-1155/chrome-linux/chrome +Chromium 133.0.6943.16 +[3662:3678:0521/170431.154477:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.154673:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.154683:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.154691:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.154709:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3678:0521/170431.187598:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.203621:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3678:0521/170431.203641:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3662:0521/170431.285455:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3678:0521/170431.285539:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3744:0521/170431.297607:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3662:3744:0521/170431.297928:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.GetDisplayDevice: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3662:3744:0521/170431.298167:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.UPower.EnumerateDevices: object_path= /org/freedesktop/UPower: org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.UPower was not provided by any .service files +[3662:3662:0521/170431.302187:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3675:0521/170431.353127:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3662:0521/170431.365407:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3674:0521/170431.365474:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3662:0521/170431.367471:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3675:0521/170431.368301:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3662:0521/170431.368437:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3675:0521/170431.368598:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3662:0521/170431.376790:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: +[3662:3675:0521/170431.376880:ERROR:bus.cc(408)] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix") +[3662:3662:0521/170431.377921:ERROR:object_proxy.cc(576)] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: + + +=== ldd chromium (shared lib deps) === +(no missing libs) diff --git a/ci-debug/20260521T170427Z-26240904544/e2e-repro.txt b/ci-debug/20260521T170427Z-26240904544/e2e-repro.txt new file mode 100644 index 0000000..a3585f0 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/e2e-repro.txt @@ -0,0 +1,26 @@ +=== Failing command === + bun run --cwd apps/web e2e + +=== Re-running with verbose output === +$ vite build && playwright test --config e2e/playwright.config.ts +vite v8.0.13 building client environment for production... + transforming...✓ 346 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-B0LNRHW3.js 399.60 kB │ gzip: 133.21 kB + +✓ built in 663ms +[WebServer] $ vite preview --port "4173" --strictPort + +Running 2 tests using 1 worker + + ✓ 1 [chromium] › e2e/pages.spec.ts:35:3 › page: home — screenshot (docs/pages/home/SPEC.md) (1.0s) + ✓ 2 [chromium] › e2e/pages.spec.ts:35:3 › page: explore — screenshot (docs/pages/explore/SPEC.md) (1.2s) + + 2 passed (3.6s) diff --git a/ci-debug/20260521T170427Z-26240904544/environment.txt b/ci-debug/20260521T170427Z-26240904544/environment.txt new file mode 100644 index 0000000..4c78c41 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/environment.txt @@ -0,0 +1,145 @@ +=== uname -a === +Linux runnervmg397c 6.17.0-1013-azure #13~24.04.1-Ubuntu SMP Wed Apr 15 16:52:17 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux + +=== node --version === +v22.22.2 + +=== bun --version === +1.3.11 + +=== bunx playwright --version === +Resolving dependencies +Resolved, downloaded and extracted [10] +Saved lockfile +Version 1.60.0 + +=== df -h /dev/shm === +Filesystem Size Used Avail Use% Mounted on +tmpfs 7.9G 84K 7.9G 1% /dev/shm + +=== df -h / === +Filesystem Size Used Avail Use% Mounted on +/dev/root 72G 54G 18G 75% / + +=== free -h === + total used free shared buff/cache available +Mem: 15Gi 1.4Gi 10Gi 47Mi 4.0Gi 14Gi +Swap: 4.0Gi 0B 4.0Gi + +=== env (filtered) === +ACCEPT_EULA=Y +ACTIONS_ORCHESTRATION_ID=1e8afadb-859b-45e2-b3b6-8f7d7550f511.screenshots.__default +ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=/opt/actionarchivecache +AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache +ANDROID_HOME=/usr/local/lib/android/sdk +ANDROID_NDK=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_NDK_LATEST_HOME=/usr/local/lib/android/sdk/ndk/29.0.14206865 +ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/27.3.13750724 +ANDROID_SDK_ROOT=/usr/local/lib/android/sdk +ANT_HOME=/usr/share/ant +AZURE_EXTENSION_DIR=/opt/az/azcliextensions +BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +CHROMEWEBDRIVER=/usr/local/share/chromedriver-linux64 +CHROME_BIN=/usr/bin/google-chrome +CI=true +CONDA=/usr/share/miniconda +DEBIAN_FRONTEND=noninteractive +DOTNET_MULTILEVEL_LOOKUP=0 +DOTNET_NOLOGO=1 +DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +EDGEWEBDRIVER=/usr/local/share/edge_driver +ENABLE_RUNNER_TRACING=true +GECKOWEBDRIVER=/usr/local/share/gecko_driver +GHCUP_INSTALL_BASE_PREFIX=/usr/local +GITHUB_ACTION=__run_6 +GITHUB_ACTIONS=true +GITHUB_ACTION_REF= +GITHUB_ACTION_REPOSITORY= +GITHUB_ACTOR=nsheaps +GITHUB_ACTOR_ID=1282393 +GITHUB_API_URL=https://api.github.com +GITHUB_BASE_REF=main +GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_ca1a4eaf-928e-48f2-9036-0962a7a79b63 +GITHUB_EVENT_NAME=pull_request +GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json +GITHUB_GRAPHQL_URL=https://api.github.com/graphql +GITHUB_HEAD_REF=claude/ai-3d-model-generator-XjoUi +GITHUB_JOB=screenshots +GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_ca1a4eaf-928e-48f2-9036-0962a7a79b63 +GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_ca1a4eaf-928e-48f2-9036-0962a7a79b63 +GITHUB_REF=refs/pull/1/merge +GITHUB_REF_NAME=1/merge +GITHUB_REF_PROTECTED=false +GITHUB_REF_TYPE=branch +GITHUB_REPOSITORY=nsheaps/farish +GITHUB_REPOSITORY_ID=1245191419 +GITHUB_REPOSITORY_OWNER=nsheaps +GITHUB_REPOSITORY_OWNER_ID=1282393 +GITHUB_RETENTION_DAYS=90 +GITHUB_RUN_ATTEMPT=1 +GITHUB_RUN_ID=26240904544 +GITHUB_RUN_NUMBER=13 +GITHUB_SERVER_URL=https://github.com +GITHUB_SHA=4b61ee5d34733aa48d7a03a04c826263df7fa7ee +GITHUB_STATE=/home/runner/work/_temp/_runner_file_commands/save_state_ca1a4eaf-928e-48f2-9036-0962a7a79b63 +GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_ca1a4eaf-928e-48f2-9036-0962a7a79b63 +GITHUB_TRIGGERING_ACTOR=nsheaps +GITHUB_WORKFLOW=Pages +GITHUB_WORKFLOW_REF=nsheaps/farish/.github/workflows/pages.yml@refs/pull/1/merge +GITHUB_WORKFLOW_SHA=4b61ee5d34733aa48d7a03a04c826263df7fa7ee +GITHUB_WORKSPACE=/home/runner/work/farish/farish +GOROOT_1_22_X64=/opt/hostedtoolcache/go/1.22.12/x64 +GOROOT_1_23_X64=/opt/hostedtoolcache/go/1.23.12/x64 +GOROOT_1_24_X64=/opt/hostedtoolcache/go/1.24.13/x64 +GOROOT_1_25_X64=/opt/hostedtoolcache/go/1.25.10/x64 +GRADLE_HOME=/usr/share/gradle-9.5.1 +HOME=/home/runner +HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS=3650 +HOMEBREW_NO_AUTO_UPDATE=1 +INVOCATION_ID=6afd18bb5d214b82a1cbf40f557801c4 +ImageOS=ubuntu24 +ImageVersion=20260518.149.1 +JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_11_X64=/usr/lib/jvm/temurin-11-jdk-amd64 +JAVA_HOME_17_X64=/usr/lib/jvm/temurin-17-jdk-amd64 +JAVA_HOME_21_X64=/usr/lib/jvm/temurin-21-jdk-amd64 +JAVA_HOME_25_X64=/usr/lib/jvm/temurin-25-jdk-amd64 +JAVA_HOME_8_X64=/usr/lib/jvm/temurin-8-jdk-amd64 +JOURNAL_STREAM=9:14606 +LANG=C.UTF-8 +LOGNAME=runner +MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/hosted-compute-agent.service/memory.pressure +MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA= +MISE_EXPERIMENTAL=1 +MISE_LOG_LEVEL=info +MISE_TRUSTED_CONFIG_PATHS=/home/runner/work/farish/farish +MISE_YES=1 +NVM_DIR=/home/runner/.nvm +PAGES_DRY_RUN=true +PATH=/home/runner/.local/share/mise/shims:/home/runner/.local/share/mise/bin:/snap/bin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +PIPX_BIN_DIR=/opt/pipx_bin +PIPX_HOME=/opt/pipx +POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-Linux +PSModulePath=/root/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules:/usr/share/az_14.6.0 +PWD=/home/runner/work/farish/farish +RUNNER_ARCH=X64 +RUNNER_ENVIRONMENT=github-hosted +RUNNER_NAME=GitHub Actions 1000074353 +RUNNER_OS=Linux +RUNNER_TEMP=/home/runner/work/_temp +RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUNNER_TRACKING_ID=github_1eeaf5df-3dfd-491e-a86a-8c97909c1c4d +RUNNER_WORKSPACE=/home/runner/work/farish +SELENIUM_JAR_PATH=/usr/share/java/selenium-server.jar +SGX_AESM_ADDR=1 +SHELL=/bin/bash +SHLVL=1 +SWIFT_PATH=/usr/share/swift/usr/bin +SYSTEMD_EXEC_PID=2050 +USER=runner +USE_BAZEL_FALLBACK_VERSION=silent: +VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg +XDG_CONFIG_HOME=/home/runner/.config +XDG_RUNTIME_DIR=/run/user/1001 +_=/usr/bin/env diff --git a/ci-debug/20260521T170427Z-26240904544/playwright-install.txt b/ci-debug/20260521T170427Z-26240904544/playwright-install.txt new file mode 100644 index 0000000..66690b7 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/playwright-install.txt @@ -0,0 +1,48 @@ +Installing dependencies... +Switching to root user to install dependencies... +Get:1 file:/etc/apt/apt-mirrors.txt Mirrorlist [144 B] +Hit:2 http://azure.archive.ubuntu.com/ubuntu noble InRelease +Hit:6 https://packages.microsoft.com/repos/azure-cli noble InRelease +Hit:7 https://packages.microsoft.com/ubuntu/24.04/prod noble InRelease +Hit:3 http://azure.archive.ubuntu.com/ubuntu noble-updates InRelease +Hit:4 http://azure.archive.ubuntu.com/ubuntu noble-backports InRelease +Hit:5 http://azure.archive.ubuntu.com/ubuntu noble-security InRelease +Hit:8 https://dl.google.com/linux/chrome-stable/deb stable InRelease +Reading package lists... +Reading package lists... +Building dependency tree... +Reading state information... +libasound2t64 is already the newest version (1.2.11-1ubuntu0.2). +libatk-bridge2.0-0t64 is already the newest version (2.52.0-1build1). +libatk1.0-0t64 is already the newest version (2.52.0-1build1). +libatspi2.0-0t64 is already the newest version (2.52.0-1build1). +libcairo2 is already the newest version (1.18.0-3build1). +libcups2t64 is already the newest version (2.4.7-1.2ubuntu7.9). +libdbus-1-3 is already the newest version (1.14.10-4ubuntu4.1). +libdrm2 is already the newest version (2.4.125-1ubuntu0.1~24.04.1). +libgbm1 is already the newest version (25.2.8-0ubuntu0.24.04.1). +libglib2.0-0t64 is already the newest version (2.80.0-6ubuntu3.8). +libnspr4 is already the newest version (2:4.35-1.1build1). +libnss3 is already the newest version (2:3.98-1ubuntu0.1). +libpango-1.0-0 is already the newest version (1.52.1+ds-1build1). +libx11-6 is already the newest version (2:1.8.7-1build1). +libxcb1 is already the newest version (1.15-1ubuntu2). +libxcomposite1 is already the newest version (1:0.4.5-1build3). +libxdamage1 is already the newest version (1:1.1.6-1build1). +libxext6 is already the newest version (2:1.3.4-1build2). +libxfixes3 is already the newest version (1:6.0.0-2build1). +libxkbcommon0 is already the newest version (1.6.0-1build1). +libxrandr2 is already the newest version (2:1.5.2-2build1). +xvfb is already the newest version (2:21.1.12-1ubuntu1.5). +fonts-noto-color-emoji is already the newest version (2.047-0ubuntu0.24.04.1). +fonts-unifont is already the newest version (1:15.1.01-1build1). +libfontconfig1 is already the newest version (2.15.0-1.1ubuntu2). +libfreetype6 is already the newest version (2.13.2+dfsg-1ubuntu0.1). +xfonts-cyrillic is already the newest version (1:1.0.5+nmu1). +xfonts-scalable is already the newest version (1:1.0.3-1.3). +fonts-liberation is already the newest version (1:2.1.5-3). +fonts-ipafont-gothic is already the newest version (00303-21ubuntu1). +fonts-wqy-zenhei is already the newest version (0.9.45-8). +fonts-tlwg-loma-otf is already the newest version (1:0.7.3-1). +fonts-freefont-ttf is already the newest version (20211204+svn4273-2). +0 upgraded, 0 newly installed, 0 to remove and 6 not upgraded. diff --git a/ci-debug/20260521T170427Z-26240904544/vite-build.txt b/ci-debug/20260521T170427Z-26240904544/vite-build.txt new file mode 100644 index 0000000..7802469 --- /dev/null +++ b/ci-debug/20260521T170427Z-26240904544/vite-build.txt @@ -0,0 +1,14 @@ +$ vite build +vite v8.0.13 building client environment for production... + transforming...✓ 346 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.41 kB │ gzip: 0.29 kB +dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 403.21 kB +dist/assets/materialdesignicons-webfont-PXm3-2wK.woff 587.98 kB +dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf 1,307.66 kB +dist/assets/materialdesignicons-webfont-CSr8KVlo.eot 1,307.88 kB +dist/assets/index-C3HWS3BM.css 665.04 kB │ gzip: 101.16 kB +dist/assets/index-B0LNRHW3.js 399.60 kB │ gzip: 133.21 kB + +✓ built in 680ms diff --git a/ci-debug/20260521T172518Z-26241968366-deploy/deploy-info.txt b/ci-debug/20260521T172518Z-26241968366-deploy/deploy-info.txt new file mode 100644 index 0000000..ab1aede --- /dev/null +++ b/ci-debug/20260521T172518Z-26241968366-deploy/deploy-info.txt @@ -0,0 +1,64 @@ +job: deploy-pages +run_id: 26241968366 +sha: eea1fe70c2e7dd193fc96470e080371fa161269b +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 17:25 . +drwxr-xr-x 19 runner runner 4096 May 21 17:25 .. +drwxr-xr-x 2 runner runner 4096 May 21 17:25 assets +drwxr-xr-x 7 runner runner 4096 May 21 17:25 docs +-rw-r--r-- 1 runner runner 416 May 21 17:25 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:25 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 17:25 . +drwxr-xr-x 5 runner runner 4096 May 21 17:25 .. +-rw-r--r-- 1 runner runner 664257 May 21 17:25 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 17:25 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 17:25 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 17:25 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 17:25 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 17:25 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 68 +drwxr-xr-x 7 runner runner 4096 May 21 17:25 . +drwxr-xr-x 5 runner runner 4096 May 21 17:25 .. +-rw-r--r-- 1 runner runner 29457 May 21 17:25 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 17:25 api +-rw-r--r-- 1 runner runner 8113 May 21 17:25 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:25 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 17:25 pages +drwxr-xr-x 2 runner runner 4096 May 21 17:25 research +drwxr-xr-x 2 runner runner 4096 May 21 17:25 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 17:25 . +drwxr-xr-x 7 runner runner 4096 May 21 17:25 .. +-rw-r--r-- 1 runner runner 11210 May 21 17:25 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 17:25 create-model +drwxr-xr-x 2 runner runner 4096 May 21 17:25 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 17:25 get-model +drwxr-xr-x 2 runner runner 4096 May 21 17:25 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 17:25 list-models +drwxr-xr-x 2 runner runner 4096 May 21 17:25 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 17:25 record-view +drwxr-xr-x 2 runner runner 4096 May 21 17:25 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:25 . +drwxr-xr-x 10 runner runner 4096 May 21 17:25 .. +-rw-r--r-- 1 runner runner 6743 May 21 17:25 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:25 . +drwxr-xr-x 10 runner runner 4096 May 21 17:25 .. +-rw-r--r-- 1 runner runner 6350 May 21 17:25 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T172518Z-26241968366-deploy/step-outcomes.json b/ci-debug/20260521T172518Z-26241968366-deploy/step-outcomes.json new file mode 100644 index 0000000..91dc1d3 --- /dev/null +++ b/ci-debug/20260521T172518Z-26241968366-deploy/step-outcomes.json @@ -0,0 +1,7 @@ +{ + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/ci-debug/20260521T173003Z-26242217033-deploy/deploy-info.txt b/ci-debug/20260521T173003Z-26242217033-deploy/deploy-info.txt new file mode 100644 index 0000000..5a1c56d --- /dev/null +++ b/ci-debug/20260521T173003Z-26242217033-deploy/deploy-info.txt @@ -0,0 +1,64 @@ +job: deploy-pages +run_id: 26242217033 +sha: 3bc2228bd172eb6f738f563cb2f995d92220518c +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 17:30 . +drwxr-xr-x 19 runner runner 4096 May 21 17:30 .. +drwxr-xr-x 2 runner runner 4096 May 21 17:30 assets +drwxr-xr-x 7 runner runner 4096 May 21 17:30 docs +-rw-r--r-- 1 runner runner 416 May 21 17:30 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:30 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 17:30 . +drwxr-xr-x 5 runner runner 4096 May 21 17:30 .. +-rw-r--r-- 1 runner runner 664257 May 21 17:30 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 17:30 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 17:30 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 17:30 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 17:30 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 17:30 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 72 +drwxr-xr-x 7 runner runner 4096 May 21 17:30 . +drwxr-xr-x 5 runner runner 4096 May 21 17:30 .. +-rw-r--r-- 1 runner runner 29457 May 21 17:30 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 17:30 api +-rw-r--r-- 1 runner runner 8200 May 21 17:30 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:30 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 17:30 pages +drwxr-xr-x 2 runner runner 4096 May 21 17:30 research +drwxr-xr-x 2 runner runner 4096 May 21 17:30 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 17:30 . +drwxr-xr-x 7 runner runner 4096 May 21 17:30 .. +-rw-r--r-- 1 runner runner 11210 May 21 17:30 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 17:30 create-model +drwxr-xr-x 2 runner runner 4096 May 21 17:30 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 17:30 get-model +drwxr-xr-x 2 runner runner 4096 May 21 17:30 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 17:30 list-models +drwxr-xr-x 2 runner runner 4096 May 21 17:30 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 17:30 record-view +drwxr-xr-x 2 runner runner 4096 May 21 17:30 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:30 . +drwxr-xr-x 10 runner runner 4096 May 21 17:30 .. +-rw-r--r-- 1 runner runner 6743 May 21 17:30 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:30 . +drwxr-xr-x 10 runner runner 4096 May 21 17:30 .. +-rw-r--r-- 1 runner runner 6350 May 21 17:30 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T173003Z-26242217033-deploy/step-outcomes.json b/ci-debug/20260521T173003Z-26242217033-deploy/step-outcomes.json new file mode 100644 index 0000000..91dc1d3 --- /dev/null +++ b/ci-debug/20260521T173003Z-26242217033-deploy/step-outcomes.json @@ -0,0 +1,7 @@ +{ + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/ci-debug/20260521T173538Z-26242510580-deploy/deploy-info.txt b/ci-debug/20260521T173538Z-26242510580-deploy/deploy-info.txt new file mode 100644 index 0000000..1a00323 --- /dev/null +++ b/ci-debug/20260521T173538Z-26242510580-deploy/deploy-info.txt @@ -0,0 +1,67 @@ +job: deploy-pages +run_id: 26242510580 +sha: 0a03792bf53c96ca9008c4dd74fe36c3f049dc26 +event: pull_request +ref: refs/pull/1/merge +head_ref:claude/ai-3d-model-generator-XjoUi +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 17:35 . +drwxr-xr-x 19 runner runner 4096 May 21 17:35 .. +drwxr-xr-x 2 runner runner 4096 May 21 17:35 assets +drwxr-xr-x 7 runner runner 4096 May 21 17:35 docs +-rw-r--r-- 1 runner runner 416 May 21 17:35 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:35 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 17:35 . +drwxr-xr-x 5 runner runner 4096 May 21 17:35 .. +-rw-r--r-- 1 runner runner 664257 May 21 17:35 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 17:35 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 17:35 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 17:35 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 17:35 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 17:35 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 72 +drwxr-xr-x 7 runner runner 4096 May 21 17:35 . +drwxr-xr-x 5 runner runner 4096 May 21 17:35 .. +-rw-r--r-- 1 runner runner 29457 May 21 17:35 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 17:35 api +-rw-r--r-- 1 runner runner 8200 May 21 17:35 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:35 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 17:35 pages +drwxr-xr-x 2 runner runner 4096 May 21 17:35 research +drwxr-xr-x 2 runner runner 4096 May 21 17:35 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 17:35 . +drwxr-xr-x 7 runner runner 4096 May 21 17:35 .. +-rw-r--r-- 1 runner runner 11210 May 21 17:35 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 17:35 create-model +drwxr-xr-x 2 runner runner 4096 May 21 17:35 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 17:35 get-model +drwxr-xr-x 2 runner runner 4096 May 21 17:35 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 17:35 list-models +drwxr-xr-x 2 runner runner 4096 May 21 17:35 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 17:35 record-view +drwxr-xr-x 2 runner runner 4096 May 21 17:35 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:35 . +drwxr-xr-x 10 runner runner 4096 May 21 17:35 .. +-rw-r--r-- 1 runner runner 6743 May 21 17:35 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:35 . +drwxr-xr-x 10 runner runner 4096 May 21 17:35 .. +-rw-r--r-- 1 runner runner 6350 May 21 17:35 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T173538Z-26242510580-deploy/pages-api-status.txt b/ci-debug/20260521T173538Z-26242510580-deploy/pages-api-status.txt new file mode 100644 index 0000000..cd44120 --- /dev/null +++ b/ci-debug/20260521T173538Z-26242510580-deploy/pages-api-status.txt @@ -0,0 +1 @@ +HTTP 404 diff --git a/ci-debug/20260521T173538Z-26242510580-deploy/pages-api.json b/ci-debug/20260521T173538Z-26242510580-deploy/pages-api.json new file mode 100644 index 0000000..c103c1c --- /dev/null +++ b/ci-debug/20260521T173538Z-26242510580-deploy/pages-api.json @@ -0,0 +1,5 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site", + "status": "404" +} diff --git a/ci-debug/20260521T173538Z-26242510580-deploy/step-outcomes.json b/ci-debug/20260521T173538Z-26242510580-deploy/step-outcomes.json new file mode 100644 index 0000000..ed07f13 --- /dev/null +++ b/ci-debug/20260521T173538Z-26242510580-deploy/step-outcomes.json @@ -0,0 +1,59 @@ +{ + "checkout": { + "outputs": { + "commit": "bdca4fc32e7b9596e4e6d4c55e8f2f6674544232", + "ref": "claude/ai-3d-model-generator-XjoUi" + }, + "outcome": "success", + "conclusion": "success" + }, + "mise": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "cache-nx": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "bootstrap": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "path": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "build": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "assemble": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "configure": { + "outputs": {}, + "outcome": "failure", + "conclusion": "failure" + }, + "upload": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + }, + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/ci-debug/20260521T174514Z-26243013728-deploy/deploy-info.txt b/ci-debug/20260521T174514Z-26243013728-deploy/deploy-info.txt new file mode 100644 index 0000000..a3ca482 --- /dev/null +++ b/ci-debug/20260521T174514Z-26243013728-deploy/deploy-info.txt @@ -0,0 +1,67 @@ +job: deploy-pages +run_id: 26243013728 +sha: 8cc932c80493d1550a929e15a73a93be88ab2c96 +event: pull_request +ref: refs/pull/1/merge +head_ref:claude/ai-3d-model-generator-XjoUi +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 17:45 . +drwxr-xr-x 19 runner runner 4096 May 21 17:45 .. +drwxr-xr-x 2 runner runner 4096 May 21 17:45 assets +drwxr-xr-x 7 runner runner 4096 May 21 17:45 docs +-rw-r--r-- 1 runner runner 416 May 21 17:45 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:45 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 17:45 . +drwxr-xr-x 5 runner runner 4096 May 21 17:45 .. +-rw-r--r-- 1 runner runner 664257 May 21 17:45 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 17:45 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 17:45 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 17:45 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 17:45 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 17:45 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 72 +drwxr-xr-x 7 runner runner 4096 May 21 17:45 . +drwxr-xr-x 5 runner runner 4096 May 21 17:45 .. +-rw-r--r-- 1 runner runner 29457 May 21 17:45 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 17:45 api +-rw-r--r-- 1 runner runner 8200 May 21 17:45 index.html +drwxr-xr-x 2 runner runner 4096 May 21 17:45 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 17:45 pages +drwxr-xr-x 2 runner runner 4096 May 21 17:45 research +drwxr-xr-x 2 runner runner 4096 May 21 17:45 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 17:45 . +drwxr-xr-x 7 runner runner 4096 May 21 17:45 .. +-rw-r--r-- 1 runner runner 11210 May 21 17:45 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 17:45 create-model +drwxr-xr-x 2 runner runner 4096 May 21 17:45 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 17:45 get-model +drwxr-xr-x 2 runner runner 4096 May 21 17:45 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 17:45 list-models +drwxr-xr-x 2 runner runner 4096 May 21 17:45 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 17:45 record-view +drwxr-xr-x 2 runner runner 4096 May 21 17:45 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:45 . +drwxr-xr-x 10 runner runner 4096 May 21 17:45 .. +-rw-r--r-- 1 runner runner 6743 May 21 17:45 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 17:45 . +drwxr-xr-x 10 runner runner 4096 May 21 17:45 .. +-rw-r--r-- 1 runner runner 6350 May 21 17:45 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T174514Z-26243013728-deploy/pages-api-status.txt b/ci-debug/20260521T174514Z-26243013728-deploy/pages-api-status.txt new file mode 100644 index 0000000..cd44120 --- /dev/null +++ b/ci-debug/20260521T174514Z-26243013728-deploy/pages-api-status.txt @@ -0,0 +1 @@ +HTTP 404 diff --git a/ci-debug/20260521T174514Z-26243013728-deploy/pages-api.json b/ci-debug/20260521T174514Z-26243013728-deploy/pages-api.json new file mode 100644 index 0000000..c103c1c --- /dev/null +++ b/ci-debug/20260521T174514Z-26243013728-deploy/pages-api.json @@ -0,0 +1,5 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site", + "status": "404" +} diff --git a/ci-debug/20260521T174514Z-26243013728-deploy/step-outcomes.json b/ci-debug/20260521T174514Z-26243013728-deploy/step-outcomes.json new file mode 100644 index 0000000..90d6b29 --- /dev/null +++ b/ci-debug/20260521T174514Z-26243013728-deploy/step-outcomes.json @@ -0,0 +1,59 @@ +{ + "checkout": { + "outputs": { + "commit": "26bbef2a0698b17f1bf9afb1ef3655cfbcd362ad", + "ref": "claude/ai-3d-model-generator-XjoUi" + }, + "outcome": "success", + "conclusion": "success" + }, + "mise": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "cache-nx": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "bootstrap": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "path": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "build": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "assemble": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "configure": { + "outputs": {}, + "outcome": "failure", + "conclusion": "failure" + }, + "upload": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + }, + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/ci-debug/20260521T185825Z-26246761026-deploy/deploy-info.txt b/ci-debug/20260521T185825Z-26246761026-deploy/deploy-info.txt new file mode 100644 index 0000000..072764b --- /dev/null +++ b/ci-debug/20260521T185825Z-26246761026-deploy/deploy-info.txt @@ -0,0 +1,67 @@ +job: deploy-pages +run_id: 26246761026 +sha: 1e50aad7d35521761b3ce0dc32d8b9b5fa952880 +event: pull_request +ref: refs/pull/1/merge +head_ref:claude/ai-3d-model-generator-XjoUi +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 18:58 . +drwxr-xr-x 19 runner runner 4096 May 21 18:58 .. +drwxr-xr-x 2 runner runner 4096 May 21 18:58 assets +drwxr-xr-x 7 runner runner 4096 May 21 18:58 docs +-rw-r--r-- 1 runner runner 416 May 21 18:58 index.html +drwxr-xr-x 2 runner runner 4096 May 21 18:58 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 18:58 . +drwxr-xr-x 5 runner runner 4096 May 21 18:58 .. +-rw-r--r-- 1 runner runner 664257 May 21 18:58 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 18:58 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 18:58 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 18:58 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 18:58 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 18:58 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 72 +drwxr-xr-x 7 runner runner 4096 May 21 18:58 . +drwxr-xr-x 5 runner runner 4096 May 21 18:58 .. +-rw-r--r-- 1 runner runner 29457 May 21 18:58 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 18:58 api +-rw-r--r-- 1 runner runner 8200 May 21 18:58 index.html +drwxr-xr-x 2 runner runner 4096 May 21 18:58 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 18:58 pages +drwxr-xr-x 2 runner runner 4096 May 21 18:58 research +drwxr-xr-x 2 runner runner 4096 May 21 18:58 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 18:58 . +drwxr-xr-x 7 runner runner 4096 May 21 18:58 .. +-rw-r--r-- 1 runner runner 11210 May 21 18:58 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 18:58 create-model +drwxr-xr-x 2 runner runner 4096 May 21 18:58 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 18:58 get-model +drwxr-xr-x 2 runner runner 4096 May 21 18:58 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 18:58 list-models +drwxr-xr-x 2 runner runner 4096 May 21 18:58 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 18:58 record-view +drwxr-xr-x 2 runner runner 4096 May 21 18:58 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 18:58 . +drwxr-xr-x 10 runner runner 4096 May 21 18:58 .. +-rw-r--r-- 1 runner runner 6743 May 21 18:58 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 18:58 . +drwxr-xr-x 10 runner runner 4096 May 21 18:58 .. +-rw-r--r-- 1 runner runner 6350 May 21 18:58 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T185825Z-26246761026-deploy/pages-api-status.txt b/ci-debug/20260521T185825Z-26246761026-deploy/pages-api-status.txt new file mode 100644 index 0000000..cd44120 --- /dev/null +++ b/ci-debug/20260521T185825Z-26246761026-deploy/pages-api-status.txt @@ -0,0 +1 @@ +HTTP 404 diff --git a/ci-debug/20260521T185825Z-26246761026-deploy/pages-api.json b/ci-debug/20260521T185825Z-26246761026-deploy/pages-api.json new file mode 100644 index 0000000..c103c1c --- /dev/null +++ b/ci-debug/20260521T185825Z-26246761026-deploy/pages-api.json @@ -0,0 +1,5 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site", + "status": "404" +} diff --git a/ci-debug/20260521T185825Z-26246761026-deploy/step-outcomes.json b/ci-debug/20260521T185825Z-26246761026-deploy/step-outcomes.json new file mode 100644 index 0000000..c1a6f56 --- /dev/null +++ b/ci-debug/20260521T185825Z-26246761026-deploy/step-outcomes.json @@ -0,0 +1,59 @@ +{ + "checkout": { + "outputs": { + "commit": "48f91cdb1fd1e1270465710b741b7f74ec289604", + "ref": "claude/ai-3d-model-generator-XjoUi" + }, + "outcome": "success", + "conclusion": "success" + }, + "mise": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "cache-nx": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "bootstrap": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "path": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "build": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "assemble": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "configure": { + "outputs": {}, + "outcome": "failure", + "conclusion": "failure" + }, + "upload": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + }, + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/ci-debug/20260521T190135Z-26246905266-deploy/deploy-info.txt b/ci-debug/20260521T190135Z-26246905266-deploy/deploy-info.txt new file mode 100644 index 0000000..7fd21e1 --- /dev/null +++ b/ci-debug/20260521T190135Z-26246905266-deploy/deploy-info.txt @@ -0,0 +1,67 @@ +job: deploy-pages +run_id: 26246905266 +sha: 1ff27b3e77e5e488e2d442d4b2fc4a3fdab43769 +event: pull_request +ref: refs/pull/1/merge +head_ref:claude/ai-3d-model-generator-XjoUi +=== _site listing === +_site: +total 24 +drwxr-xr-x 5 runner runner 4096 May 21 19:01 . +drwxr-xr-x 19 runner runner 4096 May 21 19:01 .. +drwxr-xr-x 2 runner runner 4096 May 21 19:01 assets +drwxr-xr-x 7 runner runner 4096 May 21 19:01 docs +-rw-r--r-- 1 runner runner 416 May 21 19:01 index.html +drwxr-xr-x 2 runner runner 4096 May 21 19:01 screenshots + +_site/assets: +total 4596 +drwxr-xr-x 2 runner runner 4096 May 21 19:01 . +drwxr-xr-x 5 runner runner 4096 May 21 19:01 .. +-rw-r--r-- 1 runner runner 664257 May 21 19:01 index-CKsCfv5j.css +-rw-r--r-- 1 runner runner 410532 May 21 19:01 index-DZAI1gHK.js +-rw-r--r-- 1 runner runner 1307660 May 21 19:01 materialdesignicons-webfont-B7mPwVP_.ttf +-rw-r--r-- 1 runner runner 1307880 May 21 19:01 materialdesignicons-webfont-CSr8KVlo.eot +-rw-r--r-- 1 runner runner 403216 May 21 19:01 materialdesignicons-webfont-Dp5v-WZN.woff2 +-rw-r--r-- 1 runner runner 587984 May 21 19:01 materialdesignicons-webfont-PXm3-2wK.woff + +_site/docs: +total 72 +drwxr-xr-x 7 runner runner 4096 May 21 19:01 . +drwxr-xr-x 5 runner runner 4096 May 21 19:01 .. +-rw-r--r-- 1 runner runner 29457 May 21 19:01 INITIAL_PROMPT.html +drwxr-xr-x 10 runner runner 4096 May 21 19:01 api +-rw-r--r-- 1 runner runner 8200 May 21 19:01 index.html +drwxr-xr-x 2 runner runner 4096 May 21 19:01 monorepo +drwxr-xr-x 13 runner runner 4096 May 21 19:01 pages +drwxr-xr-x 2 runner runner 4096 May 21 19:01 research +drwxr-xr-x 2 runner runner 4096 May 21 19:01 screenshots + +_site/docs/api: +total 52 +drwxr-xr-x 10 runner runner 4096 May 21 19:01 . +drwxr-xr-x 7 runner runner 4096 May 21 19:01 .. +-rw-r--r-- 1 runner runner 11210 May 21 19:01 API-SPEC.html +drwxr-xr-x 2 runner runner 4096 May 21 19:01 create-model +drwxr-xr-x 2 runner runner 4096 May 21 19:01 get-leaderboard +drwxr-xr-x 2 runner runner 4096 May 21 19:01 get-model +drwxr-xr-x 2 runner runner 4096 May 21 19:01 get-user-profile +drwxr-xr-x 2 runner runner 4096 May 21 19:01 list-models +drwxr-xr-x 2 runner runner 4096 May 21 19:01 list-user-models +drwxr-xr-x 2 runner runner 4096 May 21 19:01 record-view +drwxr-xr-x 2 runner runner 4096 May 21 19:01 submit-rating + +_site/docs/api/create-model: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 19:01 . +drwxr-xr-x 10 runner runner 4096 May 21 19:01 .. +-rw-r--r-- 1 runner runner 6743 May 21 19:01 SPEC.html + +_site/docs/api/get-leaderboard: +total 16 +drwxr-xr-x 2 runner runner 4096 May 21 19:01 . +drwxr-xr-x 10 runner runner 4096 May 21 19:01 .. +-rw-r--r-- 1 runner runner 6350 May 21 19:01 SPEC.html + +_site/docs/api/get-model: +total 16 diff --git a/ci-debug/20260521T190135Z-26246905266-deploy/pages-api-status.txt b/ci-debug/20260521T190135Z-26246905266-deploy/pages-api-status.txt new file mode 100644 index 0000000..cd44120 --- /dev/null +++ b/ci-debug/20260521T190135Z-26246905266-deploy/pages-api-status.txt @@ -0,0 +1 @@ +HTTP 404 diff --git a/ci-debug/20260521T190135Z-26246905266-deploy/pages-api.json b/ci-debug/20260521T190135Z-26246905266-deploy/pages-api.json new file mode 100644 index 0000000..c103c1c --- /dev/null +++ b/ci-debug/20260521T190135Z-26246905266-deploy/pages-api.json @@ -0,0 +1,5 @@ +{ + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site", + "status": "404" +} diff --git a/ci-debug/20260521T190135Z-26246905266-deploy/step-outcomes.json b/ci-debug/20260521T190135Z-26246905266-deploy/step-outcomes.json new file mode 100644 index 0000000..8583336 --- /dev/null +++ b/ci-debug/20260521T190135Z-26246905266-deploy/step-outcomes.json @@ -0,0 +1,59 @@ +{ + "checkout": { + "outputs": { + "commit": "2302553efe49dd06d8ec5d7dab58ee67df7e21f3", + "ref": "claude/ai-3d-model-generator-XjoUi" + }, + "outcome": "success", + "conclusion": "success" + }, + "mise": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "cache-nx": { + "outputs": { + "cache-hit": "true" + }, + "outcome": "success", + "conclusion": "success" + }, + "bootstrap": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "path": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "build": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "assemble": { + "outputs": {}, + "outcome": "success", + "conclusion": "success" + }, + "configure": { + "outputs": {}, + "outcome": "failure", + "conclusion": "failure" + }, + "upload": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + }, + "deployment": { + "outputs": {}, + "outcome": "skipped", + "conclusion": "skipped" + } +} diff --git a/docs/INITIAL_PROMPT.md b/docs/INITIAL_PROMPT.md new file mode 100644 index 0000000..e26cd0f --- /dev/null +++ b/docs/INITIAL_PROMPT.md @@ -0,0 +1,462 @@ +# Initial Prompt + +> This document is the verbatim prompt that started the **farish** project. +> Per `.claude/rules/always-keep-improving.md`, it MUST be reviewed before, +> during, and after every task to ensure progress is being made accurately. +> +> Project name: **farish** — named after [William Farish][farish], who +> formalized isometric projection in 1822. + +[farish]: https://en.wikipedia.org/wiki/William_Farish_(scientist) + +--- + +## The Prompt + +Make a fully deployable site in a git repo with github actions workflows that +deploys to github pages where a user can input their claude-code api key or +oauth api key and can enter a prompt and the ai will generate a 3d model with +configurable parameters for the user's request (with a pause for specifications +if not already provided). The site should also feature social functionality to +make models shareable and searchable with pages for best rated (1w/1m/1y/all), +most rated (aka popular) (1w/1m/1y/all), most viewed (same buckets). Use your +best judgement for all questions. Requirements: use ts/bun, monorepo +architecture, use nx for monorepo tasks, mise for org-wide-tasks +(format, lint, test, build) and tool installs, direnv for shell init when cd +into folder. Reuse scripts for local dx and CI scripting. Use off-shelf actions +instead of making your own (where appropriate). Make site desktop default, but +mobile friendly. Use an off the shelf design system. + +Work in this order, always work iteratively, use sequential thinking before +each task, during each task, and after each task to evaluate your work and +ensure it is complete (and tested by automation to the best of your ability). +After each step you MUST test your work. As soon as you have a server that +launches, each validation loop (after every change) must (using CI) +automatically take screenshots of each page (playwright + jest + well defined +page tests matching the page specs + recording videos + runs uploaded to +github pages to be viewable in browser) on test runs on `main` and upload them +(with a `[no ci]` and a date based name/path) so progress can be tracked. Do +not start writing code until these instructions tell you to: + +1. Set up claude code to use nsheaps/agents and nsheaps/ai-mktpl as + marketplaces. Dump this entire prompt into ./docs/INITIAL_PROMPT.md and + review it after every task to ensure you're on the right track. +2. Install: + * `cron-utils@agents` + * `shared-lib@ai-mktpl`: true, + * `dangerous-bypass@ai-mktpl`: true, + * `agentic-behavior@ai-mktpl`: true, + * `common-sense@ai-mktpl`: true, + * `deep-research@ai-mktpl`: true, + * `edit-utils@ai-mktpl`: true, + * `github@ai-mktpl`: true, + * `github-app@ai-mktpl`: true, + * `mise@ai-mktpl`: true, + * `scm-utils@ai-mktpl`: true, + * `sequential-thinking@ai-mktpl`: true, + * `task-utils@agents`, + * Hookify from the official repo + * Plugin-dev from the official repo +3. Do proper claude-code-web onboarding for this repo. Set it up for bypass + permissions in the settings. +4. In `.claude/rules/always-keep-improving.md` + * It should read: + 1. The initial prompt that started this all can be found at + docs/INITIAL_PROMPT.md and should be reviewed to ensure progress is + being made accurately. + + You can do literally anything. The skills you will make and have made + make what you do possible. To do something new, all you need to do is + make a skill. Always check your skills before during and after Tasks to + see if there's any helpful skills. Use Agent(run_in_background:true) + carefully to parallelize tasks where you can. These skills are only as + good as their instructions. + + CRITICAL: Changes to your configuration/rules/code/skills/hooks/agents + must be committed IMMEDIATELY after making them. + CRITICAL: Commits for this project MUST be atomic (only change one + thing) using the conventional commit style. Push often. Use CI to your + advantage, but local is always faster. + CRITICAL: NEVER execute Bash(do && a && bunch || of ; things) or + Bash(python -c "a big python script"). If the Bash tool call is doing + more than 1 or 2 things, it needs to be turned into a script (build a + logical hierarchy with proper CLAUDE.md documentation throughout in + .claude/scripts/paths/to/categorize/meaningful-name.sh. ALWAYS check to + see if a script exists before creating a new one. ALWAYS know which + scripts you'll need before executing a plan. ALWAYS make sure the + scripts are tested. ALWAYS share code between scripts when possible. + ALWAYS prefer bun/ts/compiled tool (but scripts are okay). ALWAYS use + monorepo tooling, even in the scripts, they're code too! + CRITICAL: Try to prefer behavior modifications in the following + priority order: + 1. Hooks in claude configs (remember don't keep any in + settings.local.json they're not saved!) + 2. Skill(context:fork) + 3. Agent() - don't forget they're resumable! Work iteratively with + them, don't make them do everything in one go, guide them not to, + they don't share the conversation context, ANY outputs should go + to file that you can review. ALWAYS run them in the background AND + check their work. + - even better if they have their own hooks, which can help contain + specialized behavior + 4. Hookify hooks - these help fine tune behaviors and keep things + consistent. Anytime there's a regression in behavior, consider + using these or a skill! + 5. Skills still, with scripts to encapsulate behavior + 6. Scripts + 7. Rules. These take up context and aren't really actually + enforceable. + Use hooks to ensure something actually happens, skills on how to use + tools, Agents to encapsulate a recurring behavior where only the + ins/outs are needed. + CRITICAL: NEVER use the general-purpose agent. ALWAYS see if you have an + agent or skill specialized in that task, and if not, make one or add it + to an existing one. + CRITICAL: KEEP SKILLS SMALL. Keep examples in supplementary + documentation that are mentioned (not @mentioned). Make most skills an + ordered list of instructions. Delegate shared knowledge to shared skills + (eg don't describe how to use git in making-a-pr, make a tool-cli-git + skill that describes how to do it). Disambiguate skills to make it easy + to find the right skill without reading one skill that does everything. + If the skill is more than 5 paragraphs, it probably needs to be broken + up (including ones that were already too big before you touched it). + Always break things into maintainable and shareable chunks + CRITICAL: Set up a cron for every 15 minutes to do a full review of your + configuration using "audit-verify-improve-agent" (create if it doesn't + exist). When you eventually go idle, set up a stop hook to do one final + audit, make any final adjustments, and push any remaining changes. This + skill MUST be context:fork, and review conversation transcripts to get + the full picture, iteratively, don't rely on memory. Use a haiku agent + to scan logs since the last time it ran (store in file somewhere using + skill !`date | tee .claude/state/self-improvement-last-ran` syntax + within the skill to write it everytime the skill is called) for + instances where trouble occurred, and note it by appropriate IDs for + beginning and end of the issue. use a script to extract the conversation + transcript between those IDs, then another script to convert it from + JSONL to a more readable, chat-style output (like one that you'd get + from claude-stream from nsheaps/claude-utils) + CRITICAL: Be verbose in the data you give an Agent, explicit about the + actions you want it to take and not to take, requirements (including + what you want for the outputs and details about what you want them to + contain), validation steps, restrictions, other-notes. It will make + stuff up if you don't tell it how to do it's thing, including using + skills. Use resuming to your advantage. Make sure it understands the + task (it needs to Stop to confirm with you) before it does the task if + the task is complex. Check it's outputs, don't always assume they're + right. + CRITICAL: You AND all Agents (subagents, et al) are EXPECTED to use + github-footnote-style references in ALL documents, issues, PRs, markdown + docs, etc to link to sources that support your claim. Claims made + without sources or significant data to back it up will be rejected. Use + the repo's .claude/sources/xxx/yyy/zzz.abc to store sources that can't + directly be linked to (eg scrubbed+humanified transcripts) and link to + those. Always reference files inside the repo using github.com/… links, + NEVER filesystem paths. Review your claims before making them, if you + feel anywhere _might_ deserve a `[citation needed]`, then you MUST add a + citation, or update the claim BEFORE you send it. + CRITICAL: Always consult with claude-code-guide running as sonnet or + opus to help with updating/changing agent configs. Encourage it to + always be thorough in it's research. Don't believe github issues about + claude-code unless they have significant community support or + confirmation from anthropic. + CRITICAL: If you ever achieve undesired, unplanned, failed, or errored + results when executing tools, skills, following rules, being guided by + hooks, executing agents, you MUST update the appropriate + configuration/code/document/skill + CRITICAL: After completing every task, THINK HARD about what you did + during the task (use an Agent to review the transcript if necessary) and + which skills you executed during the task. Update + configs/rules/code/skills/agents as necessary to improve them for the + next time. + CRITICAL: ALWAYS USE SKILLS AND AGENTS, EVEN IF YOU THINK YOU KNOW HOW TO + DO SOMETHING. + CRITICAL ALWAYS use /scm-utils:commit to commit code + CRITICAL: images use up insane amounts of context. ONLY use them within + an Agent(). Be explicit about what you want to know about the image. + + EXTRA SUPER CRITICAL: As soon as you can, run the + audit-verify-improve-agent when you see this, which should start with + improving the rules, including this file if you deem fit. Make sure + you're only keeping things in rules that need to be visible all the + time, and REALLY encouraging the use of Hooks, Skills, and Agents +5. Think about the pages you'll have on the site, compile them into + docs/pages/INDEX.md +6. Review the docs/pages/INDEX.md doc and make any changes you think + appropriate. Remember, just a draft. Page titles and maybe one or two + sentences about each. +7. VERIFY If you made any changes go back to step 4, and repeat step 4 until you + review the document and can make no more improvements. +8. For each page in pages/INDEX.md, make a docs/pages/page-name/SPEC.md, nothing + should go in to that spec yet. It should be just an empty file. +9. VERIFY Review what you've created in the file system against PAGES.txt and + ensure it is correct. +10. Based on what you think you'll need to know about each page and the site + overall, create docs/pages/PAGE-SPEC.md which describes the shape of these + page SPEC.md documents. Be specific about format and content. Provide + examples of good and bad. +11. Create a companion skill in .claude/skills/spec-writing/SKILL.md that + details the process of defining the resources, creating individual + resources, defining the shape of those resources, filling in the resources, + validating them, etc + * Create subskills to help with drafting and filling out each section + * Make sure the skill gives a sequential step-by-step numbered list + instruction. + * Instructions can be "run Skill(skill-name) to do a specific task that you + describe to the user" +12. Use that skill for each spec you need to write, for each page work + iteratively + * Using the drafting skills, Create an outline of the spec + * Using the second stage drafting skills, For each section add a few bullets + describing needed info in each and any important information, ESPECIALLY + references (and their human readable version) + * Using the draft review skill, Review each and ensure your plan is sound, + use sequential thinking to evaluate it + * Using the corresponding skills, write each section + * Using the review skill, go over the spec again and iterate until there's + no more improvements to make. If you make changes, you MUST perform the + validation again +13. Review the specs as a whole and make any adjustments necessary +14. Design a page-wireframing skill that will help you keep your wireframes + consistent for each page you make. +15. For each page, draft wireframes of the design. You'll use an off the shelf + design system to help here. + * Later we'll make rough outlines of these pages with mock data + * Make at least one wireframe for the whole page, and each modal window, or + popover to show in detail how it looks. Start with an ascii version, then + a drafty image based version, and then one more round of improvement + * Review your work, compare the wireframes against the spec, are all the + features of the page aligned with the spec and vice-versa? Did you learn + anything from visually designing the website? Make any adjustments + necessary. +16. Review all the pages and their relation to each other and do another + validation improvement iteration loop +17. Design a page-to-api-requirements skill that will help you extract all + needed information from your specs and wireframes to design your RESTful api + implementation. You may use a websocket where necessary to provide realtime + 2-way streams (eg if you want for the agent loop displaying steps in the + browser) +18. Using the skill, go over each page and extract requirements into + docs/api/endpoint-slugified-name/SPEC.md +19. Go over each page again and review in comparison to the api SPECs and ensure + everything is accurately tracked. Keep iterating until you can perform a + full iteration and not extract any more information. +20. Review the API specs as a whole and perform an iterative + validation/improvement loop +21. NOW WE CAN START WRITING CODE. Requirements need validation. Start by making + a validate-change skill as a spec. That skill should be a breakout to other + skills identifying the type of change (code/infra/ci/docs/…/etc). Keep these + up to date as you keep learning more about what you write for each. +22. Start with a framework for a monorepo using bun/ts, nx as the monorepo task + runner (make sure the task graph has proper dependencies and caching), mise + as the tool manager, and direnv as the shell initialization. They should all + use the same linting and formatting tools. Use custom rules to help remove + patterns we don't want in the codebase, but it doesn't have to be ESLint. + Write docs for each of these that act as specs to work against and educate + users on how to use/configure them/how they work + * Each bun package needs the following runscripts: + 1. lint + 2. format (aka lint --fix) + 3. test (even if it's just `echo "This package has no tests"`) + 4. build + 5. (optional) release (includes packaging, which if needed should also be + a separate script) +23. Create a basic folder structure of the repo that will hold different types + of packages that you'll use for this app. wireframe of a repo with a few + dummy packages in each. + * services/ - things that run and provide an api or background task (like + mcp servers for the agent) + * apps/ - things you execute and exit (like clis) + * plugins/ - a claude plugin marketplace. + * lib/ - packages that contain shared logic between the other packages. + These are leafs and only depend on other libs + * packages/ - things that get published that aren't services/apps/plugins, + they might wrap libs + * infra/ - infra as code automation, primarily for deploying the app + * .github/ full github CI integration with pull request template (which you + review when making a PR, and update as needed), deploy/release workflows + * Mise.toml - tool configs + * .mise/ - any mise scripts (anything that isn't a one-liner in a bun + package.js that nx would need to run should be captured as a mise script) + * …any others you deem relevant +24. Test the monorepo setup locally and in CI. user touchpoints to run commands + should be the same ran in CI, especially using NX in CI properly +25. Create a do-some-work skill that guides you on taking your Task from your + internal task tracking, updating the Task as you work on it, and how to use + other skills to implement the task, implement automated validation for the + task, run the validation and make sure that works right, push it and ensure + it works right in CI, etc. Make it a rough outline just so you don't forget + steps, including validation steps (and testing through automation and + ensuring screenshots from CI) +26. Build a basic framework for the api server using a microservice + architecture (though the development environment would always service all + requests with the same single API server, the browser app (vue powered, NOT + REACT), the dev front end/prod front end with proper live reload etc. Keep + in mind the focus that this should be a browser-only app to the best of our + ability, and should be deployable to github pages for now. Things that we've + discussed so far that aren't possible in that mechanism (eg without an API + server) should get a coming soon page with a ghost-ish wireframe behind it + of that page (generated using the test data and lorem impsum text/image + generators). + Add a setup using tilt (tilt-dev/tilt) and using local_resources (do NOT use + containers, all native apps, since we'll be using github pages for now). + Make a basic example of all working together including tests, not the pages + themselves. + Any publishing should be to ghcr.io. +27. Add a CI workflow that uses `tilt ci` to test the dev workflow end to end. +28. Validate that from here forward, the initial requirements of "CI always + works, CI tasks reflect and follow the same codepaths as the local dev + tooling, CI always takes pictures on main of every page, always updates docs + pages to include those new screenshots, always commits them with [skip ci], + always publishes to github actions in the same workflow so the progress can + be followed. Always publishes the specs for all features, pages, apis, + wireframes, etc to the github pages docs sites on every push to main, etc, + etc. All of these MUST be called out in the do-some-work and + validate-app-progress skills (or whatever) appropriately (at this point you + can see why we capture this entire prompt and why we want you to start with + improving this stuff before you even get started. If you understand this, + when you start working on this project as a whole (when you do the first + audit-verify-improve-agent cycle, you must address the user and tell them + explicitly (filling in the blanks). Caps required, but responses in general + do not need to be capitalized.: + * THE RULES OF THIS PROJECT TOLD ME TO STATE THE FOLLOWING TO YOU BEFORE WE + GET STARTED ON THIS PROJECT. FROM HERE ON OUT, I WILL WORK AUTONOMOUSLY AND + TIRELESSLY USING /goal AND /loop MYSELF WITHOUT ANY HUMAN INTERACTION UNTIL + THE GOAL IS ACHIEVED. THIS MESSAGE IS CAPITALIZED WITH THE PREFILLED TEXT + ONLY TO ENSURE IT IS THE MESSAGE FROM THE PROMPT AND TO EASILY + DIFFERENTIATE MY ANSWERS, BUT MY RESPONSES IN GENERAL DO NOT NEED TO BE + CAPITALIZED. + + BEFORE WE GET STARTED, PLEASE NOTE THE FOLLOWING: + - THE CURRENT DATETIME IS: ____you fill this in_____ + - BEFORE STARTING TO WORK ON THE PROJECT AS A WHOLE, I'M GOING TO START BY: + ____you fill this in____ + - IN ORDER TO GET TO THIS POINT, I'VE NEEDED TO DO SOME WORK TO ENSURE THE + RULES WILL BE FOLLOWED. SO FAR I'VE DONE: + - ___you fill this in____ + - ___you fill this in____ + - ___you fill this in____ + - ___you fill this in____ + - I HAVE TESTED MY INTERNET SEARCHING AND RESEARCH ABILITY, AND HAVE + ANSWERED THE FOLLOWING QUESTION: + QUESTION: THE WEATHER FOR ___your location, as determined by basic + websites you can find by searching___ FOR THE NEXT 7 DAYS + ANSWER: + ____you fill this in with a tabular representation of the highs, lows, + UV, precip, humidity, wind, weather, etc for each of the next 7 days. + + I WILL BUILD THIS PROJECT ITERATIVELY, FOLLOWING THIS ROUGH OUTLINE TO GET + TO OUR FINAL GOAL: + 1. ___you fill this in___ + 2. ___you fill this in___ + 3. ___you fill this in___ + + LETS GET STARTED! I'M GOING TO START BY + ____what you are about to do____ +29. Validate or ensure using plugins that the agent can view the dev version of + the site using tools to capture images. If you are unable to achieve this + after at least 10 minutes of trying, you MUST use CI to take pictures for + you, create any needed workflows and update your rules/skills to guide you + to use CI to take pictures when you need to see something. +30. Iteratively stub out each page, perform a validate/improve loop +31. Iteratively update each page to match it's wireframe, perform a + validate/improve loop +32. Iteratively review each page, improve the styles and consistency between + pages, use lorem imsum text and pictures as mock data to make the pages work + without any real data (including a mock conversation for the actual + feature), perform a validate/improve loop +33. Iteratively stub out api, perform a validate/improve loop +34. Iteratively connect each page to it's appropriate apis, perform a + validate/improve loop. Remember, we want to keep this as browser-only as + possible for now, and we want AI use to use a user's login (preferrably + login-with-claude but I think they'll need to provide a token). + * Use the claude agent sdk +35. Iteratively stub out api's functionality, delegating data access to model + abstractions, perform a validate/improve loop +36. Review all the work. When you create your final workflow when you START + doing this work, you should fill in any steps here that you'll need to get + to the end +37. ONLY when everything works (I won't accept "I got stuck"... I know you, get + around it, you can do it) in CI, github pages deployment with docs, etc and + you can no longer make progress without me inputting a key (which of course + the site should instruct the user to do it), should you stop doing work. Do + everything you can to add hooks to Stop and skills and everything to make + sure you have achieved the goal before stopping (but if you must stop, just + Stop twice in a row without any other tool calls and the session will stop) + +--- + +## Amendments + +Requirements added by the user after the original prompt was issued. They do not +replace any step above — they constrain how the relevant steps are implemented. +Cross-references point at the step numbers in [The Prompt](#the-prompt) they most +affect. + +A1. **Repo configuration as code.** Configure the repository with the + `repository-settings` GitHub App[^repo-settings] (already installed by the + user) — branch protection / branch restrictions and all repo configuration + declared in `.github/settings.yml`, not clicked in the UI. *Affects steps + 22–23.* + +A2. **Dependency automation with Renovate.** Adopt Renovate[^renovate] for + dependency updates, extending the shared config at + `nsheaps/renovate-config`[^nsheaps-renovate] (e.g. + `"extends": ["github>nsheaps/renovate-config"]`). *Affects steps 22–23.* + +A3. **Backend on Vercel (free plan) if required.** The app should stay + browser-only where possible, but realistically a backend will be needed. If + one is, host it on Vercel[^vercel] — **free plan only**. Document the entire + onboarding with screenshots: account signup, project/app creation, + environment-variable and secret syncing, and wiring CI to deploy to Vercel. + *Affects steps 26, 28, 33–35.* + +A4. **Cross-repo workflow sync.** `nsheaps/.github`[^nsheaps-dotgithub] hosts + workflows that sync shared CI/configuration across the org's repos. Account + for them — onboarding farish may require opening a PR against + `nsheaps/.github`. *Affects step 23.* + +A5. **PR dry-run CI gating.** Anthropic restricts this agent to the branch + `claude/ai-3d-model-generator-XjoUi`, so CI cannot be exercised on `main` + yet. Every CI workflow MUST support a **dry-run mode** that runs inside this + PR — including executing the code we write — to validate the workflow's + functionality before it reaches `main`. The dry-run is gated by an + environment variable provisioned through the `repository-settings` + configuration from A1. *Affects steps 24, 27, 28.* + +A6. **Diagrams in documentation.** Documentation must use diagrams to explain + concepts and graphical ideas — architectures, data flows, state machines, + sequences. Use Mermaid[^mermaid] diagrams (they render natively on GitHub + and GitHub Pages). Documentation is written after the plan exists but before + any code is written. *Affects steps 5–22.* + +[^repo-settings]: Repository Settings App — +[^renovate]: Renovate documentation — +[^nsheaps-renovate]: nsheaps/renovate-config — +[^vercel]: Vercel — +[^nsheaps-dotgithub]: nsheaps/.github — +[^mermaid]: Mermaid — + +### Environment incompatibility log + +* **`task-utils` write-gate vs. Claude Code on the web.** The `task-utils@agents` + plugin's `require-task-in-progress.sh` PreToolUse hook blocks all + Write/Edit/MultiEdit/NotebookEdit unless a Task is `in_progress`, but the + `TaskCreate`/`TaskUpdate` tools are not enabled in the Claude Code on the web + context — making the gate unsatisfiable. Fix in flight: a branch on + `nsheaps/agents` adds an env-var opt-out to the hook; farish opts out via + `.claude/settings.json`. + +--- + +## Notes captured at kickoff + +* **Target repository:** `nsheaps/farish` (clarified by the user after the + prompt was issued). +* **Development branch:** `claude/ai-3d-model-generator-XjoUi`. +* **Environment constraints** (Claude Code on the web, remote ephemeral + container): + * Plugins/marketplaces declared in `.claude/settings.json` take effect on the + next session start, not the session that writes them. + * No OS-level cron is available; the recurring "every 15 minutes" audit is + implemented as a scheduled GitHub Actions workflow plus the `/loop` + mechanism. diff --git a/docs/api/API-SPEC.md b/docs/api/API-SPEC.md new file mode 100644 index 0000000..a9b7878 --- /dev/null +++ b/docs/api/API-SPEC.md @@ -0,0 +1,211 @@ +# API Spec — Shape & Authoring Rules + +This document defines the **shape** of an endpoint specification: the +structure, required sections, and format that every +`docs/api//SPEC.md` must follow. It is the contract an +endpoint spec is reviewed against (prompt steps 17–20).[^1] + +It is not itself an endpoint spec — it is the schema for them. + +## Why a fixed shape + +Every endpoint spec is consumed downstream at three stages: + +1. **API review** (prompt steps 19–20) — the review loop diffs each spec + against this document to find what is missing or inconsistent. +2. **Implementation** (prompt step 33–35) — request/response schemas and + error tables become the server implementation checklist and contract + tests. +3. **Client integration** (prompt step 34) — source-pages links let the + frontend know exactly which page uses which endpoint. + +A consistent shape means a reviewer (human or agent) can diff a spec +against this document and mechanically find what is missing. + +## File location & naming + +- One spec per distinct operation: `docs/api//SPEC.md`. +- `` is lowercase kebab-case, combining HTTP method and + resource path segments: `list-models`, `get-model`, `create-model`, + `submit-rating`, `get-leaderboard`, `get-user-profile`, + `list-user-models`. Omit path-param segments (`:id`, `:username`). +- The spec file is always named `SPEC.md` (uppercase). +- `GET /models` and `POST /models` are separate files: + `docs/api/list-models/SPEC.md` and `docs/api/create-model/SPEC.md`. + +## Required frontmatter + +Every spec opens with a YAML frontmatter block. All keys are required. + +```yaml +--- +name: List Models # human-readable operation name +slug: list-models # matches the directory name +transport: rest # one of: rest | websocket +method: GET # HTTP method (REST only); omit for WebSocket +route: /models # URL path pattern; ":param" for dynamic segments +auth: none # one of: none | optional | required +status: draft # one of: draft | reviewed | live +source_pages: # page slugs whose specs require this endpoint + - explore + - home +--- +``` + +## Required sections + +Sections appear in this exact order. Use the exact `##` headings shown. +A section with nothing to say still appears with `_None._` — never delete +a heading. + +| # | Heading | Content | +|----|--------------------------------|------------------------------------------------------------------------------------------------------------| +| 1 | `# ` | The H1, matching `name` in frontmatter. | +| 2 | `## Summary` | 1–3 sentences: what this endpoint does and why it exists. | +| 3 | `## Transport` | One sentence: REST or WebSocket and the reason for the choice. | +| 4 | `## Method & Route` | HTTP verb + URL pattern (REST); or `## Channel & Protocol` for WebSocket (channel name, message direction). | +| 5 | `## Request` | Sub-sections: **Path params**, **Query params** (GET), **Body** (POST/PUT/PATCH), **Headers**. Each param in a table. | +| 6 | `## Response` | Success status code; response body field table (field, type, nullable, description). | +| 7 | `## Auth` | Auth scheme (bearer token / session cookie / none) and what happens when auth is absent or invalid. | +| 8 | `## Errors` | Table: HTTP status → condition → response body shape. | +| 9 | `## Source Pages` | List of page slugs (with links to their SPEC.md) that consume this endpoint. | +| 10 | `## Open Questions` | Unresolved design decisions. `_None._` when empty. | +| 11 | `## References` | Markdown footnote definitions backing non-obvious claims. | + +## Format rules + +- **Markdown**, GitHub-flavored. Wrap prose at ~90 columns. +- **Diagrams use Mermaid** (amendment A6 of the initial prompt) for + sequence or state diagrams when the flow is complex enough to warrant + one. Tables are preferred for simple request/response shapes.[^2] +- **Cite sources** with GitHub footnote syntax (`[^1]`) in `## References`. + Any claim that could attract a `[citation needed]` must carry one. +- **Link to repo files** with `github.com/...` URLs or repo-relative links, + never filesystem paths. +- Reference source page specs as `../../docs/pages//SPEC.md`. +- Keep an endpoint spec under ~100 lines. If a resource has many + operations, split them into separate files rather than growing one file. + +## Error body conventions + +All endpoint error responses share a common JSON shape. There are two patterns: + +**Standard error** (single code, no extra data): + +```json +{ "error": "" } +``` + +**Validation error** (multi-field validation failure, 400 only): + +```json +{ "error": "validation_error", "fields": ["", ...] } +``` + +The `error` code is a snake_case string (e.g. `model_not_found`, +`unauthorized`, `internal_error`). Every endpoint's `## Errors` table lists +all expected codes. Every endpoint must include at least one `500 internal_error` +row. + +**Auth-optional endpoints** that receive an invalid bearer token should +document their fallback behaviour (e.g. silently ignore → unauthenticated +path, or reject with 401). Never leave this ambiguous. + +## Array query-param serialization + +Multi-value query params (e.g. `filter`) are serialised as **repeated +params** in the query string: + +``` +GET /models?filter=cats&filter=dogs +``` + +Do not use comma-separated values (`?filter=cats,dogs`) or bracket notation +(`?filter[]=cats`). All endpoint specs follow this convention. + +## REST vs WebSocket decision + +Use **REST** for all request-response operations in farish's social layer: +fetching resources, searching, filtering, paginating, submitting mutations, +incrementing counters. + +Use **WebSocket** only when the server must push a sequence of asynchronous +messages over a long-lived connection (real-time generation progress, +collaborative editing, live notifications). For the current browser-only +architecture, AI generation streams via the Claude Agent SDK client-side +and is NOT a farish endpoint — reserve WebSocket specs for future +server-side features. + +## Good vs. bad + +### `## Request` — good + +```markdown +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------|--------|----------|-----------------------| +| `:id` | UUID | yes | Unique model ID | + +**Query params:** _None._ + +**Body:** _None._ +``` + +Why it is good: each param is named, typed, and described — a builder and +a contract test can both act on it. + +### `## Request` — bad + +```markdown +## Request + +Pass the model ID in the URL. +``` + +Why it is bad: no param name, no type, no table — gives implementation +nothing to verify against. + +### `## Errors` — good + +```markdown +## Errors + +| Status | Condition | Body | +|--------|------------------------------------|-------------------------------------------| +| 404 | Model not found or not public | `{ "error": "model_not_found" }` | +| 401 | Bearer token missing or invalid | `{ "error": "unauthorized" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | +``` + +Why it is good: exhaustive, status codes are unambiguous, body shape is +specified — client-side error handling can be driven by this table. + +### `## Errors` — bad + +```markdown +## Errors + +Returns appropriate error codes when something goes wrong. +``` + +Why it is bad: no enumerated conditions — client has no contract to code +against. + +## Authoring & review flow + +Endpoint specs are authored using the `page-to-api-requirements` skill +(`.claude/skills/page-to-api-requirements/`, prompt step 17). In short: +extract remote data + backend interactions from each page spec → group by +resource → decide transport → write each endpoint spec → review against +this document → iterate. This document is the checklist that the review +step diffs against. + +## References + +[^1]: Initial prompt step 17 (defines the skill) and steps 18–20 (extract + and review loops) — [`../INITIAL_PROMPT.md`](../INITIAL_PROMPT.md). +[^2]: Amendment A6 — Mermaid diagrams in documentation — + [`../INITIAL_PROMPT.md`](../INITIAL_PROMPT.md). diff --git a/docs/api/create-model/SPEC.md b/docs/api/create-model/SPEC.md new file mode 100644 index 0000000..7ceed9f --- /dev/null +++ b/docs/api/create-model/SPEC.md @@ -0,0 +1,107 @@ +--- +name: Create Model +slug: create-model +transport: rest +method: POST +route: /models +auth: required +status: draft +source_pages: + - generate +--- + +# Create Model + +## Summary + +Publishes a locally generated model to the shared backend, making it +discoverable via Explore, Leaderboards, and Profile. Called when the user +clicks Share on the Generate page after generation completes.[^1] + +## Transport + +REST — single request-response mutation to create a new resource.[^2] + +## Method & Route + +`POST /models` + +## Request + +**Path params:** _None._ + +**Query params:** _None._ + +**Body** (`multipart/form-data` or `application/json` with base64 geometry): + +| Field | Type | Required | Description | +|---------------|--------|----------|---------------------------------------------------------------| +| `title` | string | no | Human-readable title; server derives from prompt if omitted. | +| `prompt` | string | yes | Natural language prompt used to generate the model. | +| `params` | object | yes | Generation params: `resolution`, `style`, `complexity`. | +| `geometry` | binary | yes | GLB geometry file (multipart field or base64 string).[^3] | +| `thumbnail` | binary | no | Preview thumbnail image (JPEG/PNG); server generates if absent.| + +**Headers:** + +| Header | Required | Description | +|-----------------|----------|--------------------------------------------------| +| `Authorization` | yes | `Bearer ` — Claude OAuth or API key. | +| `Content-Type` | yes | `multipart/form-data` or `application/json`. | + +## Response + +**201 Created** + +| Field | Type | Nullable | Description | +|----------------|--------|----------|---------------------------------------------------| +| `id` | UUID | no | Newly assigned model identifier. | +| `title` | string | no | Resolved title (provided or prompt-derived). | +| `geometryUrl` | string | no | Public URL to the stored GLB file. | +| `thumbnailUrl` | string | yes | Public URL to the stored thumbnail. | +| `createdAt` | string | no | ISO 8601 creation timestamp. | + +## Auth + +Required — `Authorization: Bearer ` (Claude OAuth token or API key). +Unauthenticated requests are rejected with 401.[^4] + +## Errors + +| Status | Condition | Body | +|--------|------------------------------------|--------------------------------------------| +| 400 | Missing required field | `{ "error": "validation_error", "fields": [...] }` | +| 400 | Geometry file too large or invalid | `{ "error": "invalid_geometry" }` | +| 401 | Bearer token missing or invalid | `{ "error": "unauthorized" }` | +| 413 | Payload too large | `{ "error": "payload_too_large" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/generate/SPEC.md`](../../docs/pages/generate/SPEC.md) — + Share button triggers this endpoint after generation completes. + +## Open Questions + +- **Geometry upload strategy.** Accepting a binary GLB in the POST body is + simple but limits file size. Consider a two-step flow: client requests a + pre-signed object-storage URL, uploads directly, then POSTs the URL here. + Deferred to implementation.[^5] +- **Thumbnail auto-generation.** Server-side thumbnail generation requires a + headless renderer (e.g. Puppeteer + Three.js). Accepting a client-provided + thumbnail is simpler for v1. +- **Max geometry size.** Vercel free plan has a 4.5 MB body limit; GLB files + may exceed this — pre-signed upload workaround may be mandatory.[^6] + +## References + +[^1]: Generate spec Share interaction and `create-model` backend action — + [`../../docs/pages/generate/SPEC.md`](../../docs/pages/generate/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: glTF 2.0 / GLB format — . +[^4]: Initial prompt — "user can input their claude-code api key or oauth api key" — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^5]: Amendment A3 — Vercel backend (free plan) — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^6]: Vercel free plan limits — . diff --git a/docs/api/get-leaderboard/SPEC.md b/docs/api/get-leaderboard/SPEC.md new file mode 100644 index 0000000..7b07fa6 --- /dev/null +++ b/docs/api/get-leaderboard/SPEC.md @@ -0,0 +1,107 @@ +--- +name: Get Leaderboard +slug: get-leaderboard +transport: rest +method: GET +route: /leaderboard +auth: none +status: draft +source_pages: + - leaderboards +--- + +# Get Leaderboard + +## Summary + +Returns an ordered list of up to 50 ranked models for one of three boards +(Best Rated, Most Rated / Popular, Most Viewed) filtered to a chosen time +bucket (1 week, 1 month, 1 year, or all time). Consumed by the Leaderboards +page to populate its RankedList.[^1] + +## Transport + +REST — request-response read parameterised by board type and period; no +server-push needed.[^2] + +## Method & Route + +`GET /leaderboard` + +## Request + +**Path params:** _None._ + +**Query params:** + +| Param | Type | Required | Description | +|----------|---------|----------|--------------------------------------------------------------------------------| +| `board` | string | yes | Ranking metric: `rated` \| `popular` \| `viewed`. | +| `period` | string | yes | Time window: `1w` \| `1m` \| `1y` \| `all`. | +| `limit` | integer | no | Maximum results. Default: `50`; max: `50`. | + +**Body:** _None._ + +**Headers:** _None required._ + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|---------|----------|----------|------------------------------------------| +| `board` | string | no | Active board value echoed back. | +| `period`| string | no | Active period value echoed back. | +| `items` | object[] | no | Ordered array of ranked model entries. | + +Each item in `items`: + +| Field | Type | Nullable | Description | +|---------------|---------|----------|----------------------------------------------------------------| +| `rank` | integer | no | 1-based rank position. | +| `id` | UUID | no | Unique model identifier. | +| `title` | string | no | Model title. | +| `thumbnailUrl`| string | yes | URL to the preview thumbnail. | +| `authorName` | string | no | Creator display name. | +| `authorId` | UUID | no | Creator's internal UUID. | +| `authorUsername`| string | no | Creator's public username handle (used to build `/u/:username` profile links). | +| `metricValue` | number | no | Board metric value: avg rating, rating count, or view count. | +| `createdAt` | string | no | ISO 8601 creation timestamp (for tie-break display). | + +## Auth + +None — leaderboards are publicly visible without authentication.[^3] + +## Errors + +| Status | Condition | Body | +|--------|----------------------------------|---------------------------------------------| +| 400 | Invalid or missing `board` | `{ "error": "invalid_board" }` | +| 400 | Invalid or missing `period` | `{ "error": "invalid_period" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/leaderboards/SPEC.md`](../../docs/pages/leaderboards/SPEC.md) — + BoardTabs + TimeBucketSelector drive the `board` and `period` params; + RankedList renders the response `items`. + +## Open Questions + +- **Tie-breaking rule.** When two models share the same metric value, the + recommended default is earlier `createdAt` wins; confirm at + implementation.[^4] +- **Result freshness.** Whether rankings are computed on-the-fly or from a + periodically refreshed materialised view is an implementation decision; + caching strategy affects staleness tolerance. + +## References + +[^1]: Leaderboards spec `rankings` data item (remote, read-only) — + [`../../docs/pages/leaderboards/SPEC.md`](../../docs/pages/leaderboards/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Initial prompt — "best rated (1w/1m/1y/all), most rated … most viewed" — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^4]: Leaderboards spec open question on tie-breaking — + [`../../docs/pages/leaderboards/SPEC.md`](../../docs/pages/leaderboards/SPEC.md). diff --git a/docs/api/get-model/SPEC.md b/docs/api/get-model/SPEC.md new file mode 100644 index 0000000..dc19cb1 --- /dev/null +++ b/docs/api/get-model/SPEC.md @@ -0,0 +1,105 @@ +--- +name: Get Model +slug: get-model +transport: rest +method: GET +route: /models/:id +auth: none +status: draft +source_pages: + - model-detail + - explore + - leaderboards + - profile + - home +--- + +# Get Model + +## Summary + +Returns the full record for a single publicly shared model, including its +prompt, generation parameters, geometry URL, metadata, and social counters. +Used by Model Detail to populate the viewer and info panel.[^1] + +## Transport + +REST — single request-response fetch of a known resource by identifier.[^2] + +## Method & Route + +`GET /models/:id` + +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------|------|----------|---------------------------| +| `:id` | UUID | yes | Unique shared model ID. | + +**Query params:** _None._ + +**Body:** _None._ + +**Headers:** _None required._ + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|----------------|---------|----------|---------------------------------------------------| +| `id` | UUID | no | Unique model identifier. | +| `title` | string | no | Model title (derived from prompt if not set). | +| `prompt` | string | no | Natural language prompt used to generate it. | +| `params` | object | no | Generation params: `resolution`, `style`, `complexity`. | +| `geometryUrl` | string | no | URL to the GLB geometry file.[^3] | +| `thumbnailUrl` | string | yes | URL to the preview thumbnail image. | +| `authorName` | string | no | Display name of the creator. | +| `authorId` | UUID | no | Creator's internal UUID. | +| `authorUsername`| string | no | Creator's public username handle (used to build `/u/:username` profile links). | +| `createdAt` | string | no | ISO 8601 creation timestamp. | +| `viewCount` | integer | no | Total view count. | +| `ratingAvg` | number | yes | Average star rating (1–5); null if no ratings. | +| `ratingCount` | integer | no | Number of ratings submitted. | + +## Auth + +None — shared models are publicly accessible without authentication. + +## Errors + +| Status | Condition | Body | +|--------|---------------------------------|--------------------------------------------| +| 404 | Model not found or not public | `{ "error": "model_not_found" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md) — + primary consumer; populates ThreeDViewer and MetadataPanel. +- [`../../docs/pages/explore/SPEC.md`](../../docs/pages/explore/SPEC.md) — + card click navigates to `/m/:id`, triggering this fetch. +- [`../../docs/pages/leaderboards/SPEC.md`](../../docs/pages/leaderboards/SPEC.md) — + card click navigates to `/m/:id`. +- [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md) — + card click navigates to `/m/:id`. +- [`../../docs/pages/home/SPEC.md`](../../docs/pages/home/SPEC.md) — + TrendingStrip card click navigates to `/m/:id`. + +## Open Questions + +- **Private/draft models.** The current spec covers only public shared models. + When draft/private model support is added, auth and visibility rules will + need extending.[^4] + +## References + +[^1]: Model Detail spec `model` data item (remote) — + [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: glTF 2.0 / GLB format — . +[^4]: Initial prompt step 17 — API design guidance — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). diff --git a/docs/api/get-user-profile/SPEC.md b/docs/api/get-user-profile/SPEC.md new file mode 100644 index 0000000..41b8a8e --- /dev/null +++ b/docs/api/get-user-profile/SPEC.md @@ -0,0 +1,90 @@ +--- +name: Get User Profile +slug: get-user-profile +transport: rest +method: GET +route: /users/:username +auth: none +status: draft +source_pages: + - profile +--- + +# Get User Profile + +## Summary + +Returns the public profile record for a creator: display name, avatar, +join date, and aggregate stats (total shared models, average rating received, +cumulative views). Used by the Profile page to populate the ProfileHeader.[^1] + +## Transport + +REST — request-response read of a single user resource.[^2] + +## Method & Route + +`GET /users/:username` + +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------------|--------|----------|--------------------------------| +| `:username` | string | yes | Creator's public username handle. | + +**Query params:** _None._ + +**Body:** _None._ + +**Headers:** _None required._ + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|--------------|---------|----------|-------------------------------------------------------| +| `username` | string | no | Public username handle. | +| `displayName`| string | no | Human-readable display name. | +| `avatarUrl` | string | yes | URL to the user's avatar image; null if not set. | +| `joinDate` | string | no | ISO 8601 date the account was created. | +| `modelCount` | integer | no | Total number of publicly shared models. | +| `avgRating` | number | yes | Average rating across all the user's shared models. | +| `totalViews` | integer | no | Cumulative view count across all shared models. | + +## Auth + +None — public profiles are accessible without authentication.[^3] + +## Errors + +| Status | Condition | Body | +|--------|------------------------------------|--------------------------------------------| +| 404 | Username not found | `{ "error": "user_not_found" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md) — + ProfileHeader is built from this response. + +## Open Questions + +- **Username source.** The Profile spec notes this may come from the Claude + OAuth display name or a farish-specific field; auth design resolves + this.[^4] +- **Blocking / private profiles.** No visibility controls are specified for + v1 — all accounts are publicly visible by default. + +## References + +[^1]: Profile spec `userProfile` data item (remote, read-only) — + [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Initial prompt — social layer, public profiles — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^4]: Profile spec open question on username source — + [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md). diff --git a/docs/api/list-models/SPEC.md b/docs/api/list-models/SPEC.md new file mode 100644 index 0000000..cf1bd38 --- /dev/null +++ b/docs/api/list-models/SPEC.md @@ -0,0 +1,110 @@ +--- +name: List Models +slug: list-models +transport: rest +method: GET +route: /models +auth: none +status: draft +source_pages: + - home + - explore +--- + +# List Models + +## Summary + +Returns a paginated, optionally filtered list of publicly shared models. Used +by the Explore page for browsable gallery + search, and by the Home page to +populate the TrendingStrip (most-viewed, small limit).[^1] + +## Transport + +REST — request-response read with query-parameter filtering and cursor +pagination; no server-push needed.[^2] + +## Method & Route + +`GET /models` + +## Request + +**Path params:** _None._ + +**Query params:** + +| Param | Type | Required | Description | +|----------|----------|----------|------------------------------------------------------------------| +| `q` | string | no | Full-text search over title and prompt. | +| `sort` | string | no | `newest` \| `rating` \| `popular` \| `views`. Default: `newest`. | +| `filter` | string[] | no | Tag/category slugs to AND-filter by. Serialised as repeated params: `?filter=tag1&filter=tag2`. | +| `page` | integer | no | 1-based page number. Default: `1`. | +| `limit` | integer | no | Results per page. Default: `24`; max: `100`. | + +**Body:** _None._ + +**Headers:** _None required._ + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|--------------|----------|----------|-------------------------------------------------------| +| `items` | object[] | no | Array of model card objects (see below). | +| `total` | integer | no | Total matching records (for pagination UI). | +| `page` | integer | no | Current page number returned. | +| `totalPages` | integer | no | Total number of pages. | + +Each item in `items`: + +| Field | Type | Nullable | Description | +|---------------|---------|----------|------------------------------------------| +| `id` | UUID | no | Unique model identifier. | +| `title` | string | no | Model title. | +| `thumbnailUrl` | string | yes | URL to the model thumbnail image. | +| `authorName` | string | no | Display name of the creator. | +| `authorId` | UUID | no | Creator's internal UUID. | +| `authorUsername`| string | no | Creator's public username handle (used to build `/u/:username` profile links). | +| `ratingAvg` | number | yes | Average star rating (1–5); null if none. | +| `viewCount` | integer | no | Total view count. | +| `createdAt` | string | no | ISO 8601 creation timestamp. | + +## Auth + +None — the model gallery is publicly accessible without authentication. + +## Errors + +| Status | Condition | Body | +|--------|---------------------------------|--------------------------------------------| +| 400 | Invalid `sort` value | `{ "error": "invalid_sort" }` | +| 400 | `limit` exceeds maximum | `{ "error": "limit_too_large" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/home/SPEC.md`](../../docs/pages/home/SPEC.md) — Home + TrendingStrip (`sort=views`, small limit). +- [`../../docs/pages/explore/SPEC.md`](../../docs/pages/explore/SPEC.md) — + Explore gallery with search, sort, and filter. + +## Open Questions + +- **Tag/category taxonomy.** The `filter` param's allowed values depend on a + tag vocabulary not yet defined; defer to data-model design.[^3] +- **Cursor vs. offset pagination.** Offset pagination is used here (`page=N`) + for simplicity; consider cursor-based pagination for large datasets or + real-time feeds to avoid page-drift.[^4] + +## References + +[^1]: Home spec TrendingStrip data item — + [`../../docs/pages/home/SPEC.md`](../../docs/pages/home/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Explore spec open question on filter taxonomy — + [`../../docs/pages/explore/SPEC.md`](../../docs/pages/explore/SPEC.md). +[^4]: Initial prompt step 17 — API design guidance — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). diff --git a/docs/api/list-user-models/SPEC.md b/docs/api/list-user-models/SPEC.md new file mode 100644 index 0000000..2c6ca06 --- /dev/null +++ b/docs/api/list-user-models/SPEC.md @@ -0,0 +1,104 @@ +--- +name: List User Models +slug: list-user-models +transport: rest +method: GET +route: /users/:username/models +auth: none +status: draft +source_pages: + - profile +--- + +# List User Models + +## Summary + +Returns a paginated list of publicly shared models authored by a specific +creator. Used by the Profile page to populate the ModelGrid below the +ProfileHeader.[^1] + +## Transport + +REST — request-response read with offset pagination; no server-push needed.[^2] + +## Method & Route + +`GET /users/:username/models` + +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------------|--------|----------|-----------------------------------| +| `:username` | string | yes | Creator's public username handle. | + +**Query params:** + +| Param | Type | Required | Description | +|---------|---------|----------|-----------------------------------------------| +| `page` | integer | no | 1-based page number. Default: `1`. | +| `limit` | integer | no | Results per page. Default: `24`; max: `100`. | + +**Body:** _None._ + +**Headers:** _None required._ + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|--------------|----------|----------|-------------------------------------------------------| +| `items` | object[] | no | Array of model card objects (see below). | +| `total` | integer | no | Total number of public models for this user. | +| `page` | integer | no | Current page number returned. | +| `totalPages` | integer | no | Total number of pages. | + +Each item in `items`: + +| Field | Type | Nullable | Description | +|---------------|---------|----------|------------------------------------------| +| `id` | UUID | no | Unique model identifier. | +| `title` | string | no | Model title. | +| `thumbnailUrl`| string | yes | URL to the preview thumbnail. | +| `ratingAvg` | number | yes | Average star rating; null if none. | +| `viewCount` | integer | no | Total view count. | +| `createdAt` | string | no | ISO 8601 creation timestamp. | + +## Auth + +None — a creator's public model list is accessible without authentication.[^3] + +## Errors + +| Status | Condition | Body | +|--------|---------------------------------|--------------------------------------------| +| 404 | Username not found | `{ "error": "user_not_found" }` | +| 400 | `limit` exceeds maximum | `{ "error": "limit_too_large" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md) — + ModelGrid fetches this list; Load More / pagination triggers subsequent + pages. + +## Open Questions + +- **Default sort.** Newest-first is assumed; a `sort` param (e.g. `newest`, + `rating`, `views`) may be added in a follow-up pass.[^4] +- **Pagination style.** Offset pagination used here for simplicity; cursor- + based pagination preferred for large galleries — defer to implementation. + +## References + +[^1]: Profile spec `userModels` data item (remote, read-only) — + [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Initial prompt — public profile / social layer — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^4]: Profile spec open question on pagination — + [`../../docs/pages/profile/SPEC.md`](../../docs/pages/profile/SPEC.md). diff --git a/docs/api/record-view/SPEC.md b/docs/api/record-view/SPEC.md new file mode 100644 index 0000000..c9f4efc --- /dev/null +++ b/docs/api/record-view/SPEC.md @@ -0,0 +1,92 @@ +--- +name: Record View +slug: record-view +transport: rest +method: POST +route: /models/:id/views +auth: optional +status: draft +source_pages: + - model-detail +--- + +# Record View + +## Summary + +Increments the view counter for a shared model. Called once when Model Detail +loads a remote model record. Deduplication is server-managed: authenticated +requests dedup by user identity; unauthenticated requests dedup by IP and a +short time window.[^1] + +## Transport + +REST — a fire-and-forget mutation; the client does not need the response +body to continue rendering the page.[^2] + +## Method & Route + +`POST /models/:id/views` + +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------|------|----------|--------------------------| +| `:id` | UUID | yes | Unique shared model ID. | + +**Query params:** _None._ + +**Body:** _None._ + +**Headers:** + +| Header | Required | Description | +|-----------------|----------|----------------------------------------------------------| +| `Authorization` | no | `Bearer ` — enables user-level deduplication. | + +## Response + +**204 No Content** — no response body; the client ignores this response. + +## Auth + +Optional — the endpoint accepts unauthenticated requests. If a valid bearer +token is provided it is used for finer-grained deduplication; absence does +not fail the request. An **invalid or expired** bearer token is silently +ignored — the request falls back to unauthenticated (IP-based) deduplication +rather than returning 401. This fire-and-forget design prevents view recording +from blocking the page render.[^3] + +## Errors + +| Status | Condition | Body | +|--------|---------------------------------|--------------------------------------------| +| 404 | Model not found or not public | `{ "error": "model_not_found" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md) — + fires once on load when the model is a remote (non-`local:`) record. + +## Open Questions + +- **Deduplication window.** Unauthenticated dedup by IP within a time window + (e.g. 24 hours) prevents trivial inflation; exact window TBD at + implementation.[^4] +- **View count on local models.** The Model Detail spec asks whether viewing + a local copy of a published model should increment the remote counter. + Resolved: only explicit remote fetches trigger this endpoint.[^1] + +## References + +[^1]: Model Detail spec open question on view count increment — + [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Initial prompt — "most viewed" social counter requirement — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^4]: Initial prompt step 17 — API design guidance — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). diff --git a/docs/api/submit-rating/SPEC.md b/docs/api/submit-rating/SPEC.md new file mode 100644 index 0000000..5b6b6d5 --- /dev/null +++ b/docs/api/submit-rating/SPEC.md @@ -0,0 +1,102 @@ +--- +name: Submit Rating +slug: submit-rating +transport: rest +method: PUT +route: /models/:id/rating +auth: required +status: draft +source_pages: + - model-detail +--- + +# Submit Rating + +## Summary + +Creates or replaces the authenticated user's star rating for a shared model. +PUT semantics are used so repeated calls from the same user simply update their +rating rather than creating duplicates. The response returns the updated +aggregate counters so the client can refresh the display immediately.[^1] + +## Transport + +REST — idempotent upsert mutation; no server-push needed.[^2] + +## Method & Route + +`PUT /models/:id/rating` + +## Request + +**Path params:** + +| Param | Type | Required | Description | +|-------|------|----------|--------------------------| +| `:id` | UUID | yes | Unique shared model ID. | + +**Query params:** _None._ + +**Body** (`application/json`): + +| Field | Type | Required | Description | +|---------|---------|----------|--------------------------------------------| +| `stars` | integer | yes | Star rating value — must be 1 through 5. | + +**Headers:** + +| Header | Required | Description | +|-----------------|----------|----------------------------------------------| +| `Authorization` | yes | `Bearer ` — Claude OAuth or API key. | +| `Content-Type` | yes | `application/json` | + +## Response + +**200 OK** + +| Field | Type | Nullable | Description | +|---------------|---------|----------|----------------------------------------------| +| `ratingAvg` | number | no | Updated average rating (1–5, two decimals). | +| `ratingCount` | integer | no | Updated total number of ratings. | +| `userStars` | integer | no | The star value just recorded for this user. | + +## Auth + +Required — `Authorization: Bearer `. Unauthenticated requests are +rejected with 401 so ratings are tied to an identity.[^3] + +## Errors + +| Status | Condition | Body | +|--------|---------------------------------------------|-------------------------------------------------| +| 400 | `stars` missing or out of range | `{ "error": "invalid_stars" }` | +| 401 | Bearer token missing or invalid | `{ "error": "unauthorized" }` | +| 403 | Authenticated user is the model's author | `{ "error": "self_rating_not_allowed" }` | +| 404 | Model not found or not public | `{ "error": "model_not_found" }` | +| 500 | Unexpected server error | `{ "error": "internal_error" }` | + +## Source Pages + +- [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md) — + RatingWidget triggers this endpoint; the response updates MetadataPanel + counters in place. + +## Open Questions + +- **Self-rating.** Creators may not rate their own models — the server returns + 403 (`self_rating_not_allowed`) if the token identity matches the model's + `authorId`. See Errors table.[^4] +- **Rating deletion.** No DELETE is defined here; a user who wants to "remove" + their rating would need a separate endpoint or a convention (e.g. `stars: 0`). + Deferred to implementation. + +## References + +[^1]: Model Detail spec `userRating` data item (remote, write) — + [`../../docs/pages/model-detail/SPEC.md`](../../docs/pages/model-detail/SPEC.md). +[^2]: API-SPEC.md REST vs WebSocket decision — + [`../API-SPEC.md`](../API-SPEC.md). +[^3]: Initial prompt — "best rated … most rated (aka popular)" social + requirements — [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). +[^4]: Initial prompt step 17 — API design guidance — + [`../../docs/INITIAL_PROMPT.md`](../../docs/INITIAL_PROMPT.md). diff --git a/docs/monorepo/README.md b/docs/monorepo/README.md new file mode 100644 index 0000000..3eee6e5 --- /dev/null +++ b/docs/monorepo/README.md @@ -0,0 +1,107 @@ +# farish Monorepo + +How the farish repository is structured and built. This directory is both a +**spec** (the contract the tooling must satisfy) and a **user guide** (how to +work in the repo day to day). It implements steps 22–23 of +[`docs/INITIAL_PROMPT.md`][prompt]. + +[prompt]: ../INITIAL_PROMPT.md + +## Toolchain at a glance + +| Layer | Tool | Role | Guide | +| ---------------- | ----------- | ---------------------------------------------- | ---------------------------- | +| Tool manager | **mise** | Pins bun/node/direnv; org-wide task entrypoints | [mise.md](./mise.md) | +| Shell init | **direnv** | Activates the environment on `cd` into the repo | [direnv.md](./direnv.md) | +| Runtime + PM | **bun** | JS/TS runtime, package manager, test runner | [bun.md](./bun.md) | +| Task runner | **nx** | Task graph, `dependsOn` ordering, caching | [nx.md](./nx.md) | +| Lint + format | **Biome** | One shared lint+format config + custom rules | [lint-format.md](./lint-format.md) | + +## How the layers fit together + +```mermaid +flowchart TD + subgraph shell["Your shell"] + direnv["direnv\n(.envrc)"] + end + subgraph tools["Tool management"] + mise["mise\n(mise.toml)"] + end + subgraph runner["Task running"] + nx["nx\n(nx.json)"] + end + subgraph pkgs["Per-package scripts"] + biome["Biome\n(biome.json)"] + bun["bun build / bun test"] + tsc["tsc"] + end + + direnv -->|activates| mise + mise -->|pins + runs| nx + mise -->|installs| bun + nx -->|runs lint| biome + nx -->|runs build| tsc + nx -->|runs test| bun +``` + +A developer runs `mise run `; mise invokes `nx`; nx walks the project +graph, orders tasks by their dependencies, caches results, and runs each +package's run-script. **CI runs the exact same `mise run` commands** — there is +no separate CI script path (initial prompt step 24). + +## Quick start + +```sh +# 1. Install direnv + hook it into your shell (once per machine) +mise use -g direnv +echo 'eval "$(direnv hook bash)"' >> ~/.bashrc # zsh: direnv hook zsh + +# 2. Enter the repo — direnv loads .envrc, mise installs pinned tools +cd farish +direnv allow + +# 3. Install workspace dependencies +mise run bootstrap + +# 4. Validate +mise run check # lint + test + build — the CI gate +``` + +## Org-wide task commands + +| Command | Does | +| -------------------- | ------------------------------------------------ | +| `mise run bootstrap` | `bun install` (workspace dependencies) | +| `mise run lint` | Biome lint across every package | +| `mise run format` | Biome lint --fix (autofix) across every package | +| `mise run test` | `bun test` across every package | +| `mise run build` | `tsc` build across every package (in dep order) | +| `mise run check` | lint + test + build — the full validation gate | +| `mise run graph` | Dump the nx project graph to `.nx-graph.json` | +| `mise run dev` | Bring up the local dev stack (API + web) via Tilt | + +## Repository folder structure + +See [folder-structure.md](./folder-structure.md) for the package-type layout +(`services/`, `apps/`, `lib/`, `packages/`, `plugins/`, `infra/`). + +## App framework + +The app framework skeleton — the API server, the Vue browser app, the Coming +Soon mechanism, and how they run together — is described in +[app-framework.md](./app-framework.md). Local orchestration is +[tilt.md](./tilt.md); container publishing is +[infra/ghcr.md](../../infra/ghcr.md). + +## Per-package contract + +Every bun package in the workspace **must** define these run-scripts (initial +prompt step 22): + +| Script | Purpose | +| --------- | ---------------------------------------------------- | +| `lint` | Lint with Biome. | +| `format` | Lint with autofix (`lint --fix`). | +| `test` | Run tests (`echo "no tests"` is acceptable if none). | +| `build` | Produce `dist/` via `tsc`. | +| `release` | *(optional)* Packaging/publishing — publishable pkgs only. | diff --git a/docs/monorepo/app-framework.md b/docs/monorepo/app-framework.md new file mode 100644 index 0000000..2100cad --- /dev/null +++ b/docs/monorepo/app-framework.md @@ -0,0 +1,108 @@ +# App Framework Skeleton + +How the farish application framework fits together (initial prompt step 26). + +This document describes the **skeleton** — the framework plus one worked +end-to-end example. The 11 real pages and the eight real API endpoints are +filled in by later prompt steps; this is the scaffolding they build on. + +## Pieces + +| Package | Kind | Role | +| --------------------- | ------- | ---------------------------------------------------------- | +| `@farish/api` | service | API server framework — router, `/health`, one example route | +| `@farish/web` | app | Vue 3 + Vuetify browser app — Vite dev + static GH Pages build | +| `@farish/api-contract`| lib | Shared request/response types both sides depend on | +| `@farish/mock-data` | lib | Lorem-ipsum + offline placeholder images for ghost wireframes | + +## How a request flows + +```mermaid +sequenceDiagram + participant U as Browser + participant W as Vite dev server (:5173) + participant A as API server (:8787) + U->>W: GET / (the app) + W-->>U: Vue app (HMR enabled) + U->>W: fetch /api/models + W->>A: proxied → GET /models + A->>A: router matches list-models route + A-->>W: 200 ListModelsResponse (mock data) + W-->>U: JSON +``` + +In development the Vite dev server proxies every `/api/*` request to the API +server, so the browser app calls the API same-origin (no CORS hop). In a +production GitHub Pages build the client uses `VITE_API_BASE_URL` instead. + +## Microservice architecture + +The API server is built **microservice-style**: each endpoint is an +independent route module ([`services/api/src/routes/`][routes]). One registry +([`routes/index.ts`][registry]) lists what the development server mounts — +a single process serving every route. When the API later splits into separate +deployable services, each service mounts the subset of that registry it owns; +no route module changes. + +```mermaid +flowchart LR + subgraph dev[Development — one process] + router[router.ts] --> health[GET /health] + router --> models[GET /models] + end + subgraph prod[Production — split services possible] + svcA[health service] -.same modules.-> health + svcB[models service] -.same modules.-> models + end +``` + +## Coming Soon mechanism + +farish ships as a browser-only GitHub Pages site. Pages that need a shared +backend (Explore, Leaderboards, Profile) cannot work yet, so they render the +reusable **Coming Soon** mechanism: the eventual page content drawn as a +dimmed, blurred "ghost wireframe" (populated by `@farish/mock-data`) behind a +non-dismissible Coming Soon card. + +```mermaid +flowchart TB + view[ExploreComingSoonView] --> cs[ComingSoon component] + cs --> ghost[Ghost wireframe — slot content, dimmed + blurred] + cs --> card[Coming Soon card — overlay] + ghost --> grid[GhostModelGrid — mock ModelCards] + grid --> md[@farish/mock-data] +``` + +The component ([`ComingSoon.vue`][coming-soon]) takes the eventual page as a +slot, so every backend-gated page reuses it. `ExploreComingSoonView` is the one +worked example; the others are added with their pages in later steps. + +## Running it locally + +[Tilt](./tilt.md) brings the API server and the Vue dev server up together as +native processes: + +```sh +mise run dev # → tilt up +``` + +See [tilt.md](./tilt.md) for the resource graph and ports. + +## Publishing + +The static web app deploys to GitHub Pages. The API service is packaged as a +container image published to ghcr.io — see [infra/ghcr.md](../../infra/ghcr.md). +Local development never uses containers. + +## See also + +- [folder-structure.md](./folder-structure.md) — the package layout. +- [tilt.md](./tilt.md) — local dev orchestration. +- [infra/ghcr.md](../../infra/ghcr.md) — container publishing. +- [docs/api/API-SPEC.md](../api/API-SPEC.md) — the endpoint-spec contract. +- [docs/pages/coming-soon/SPEC.md](../pages/coming-soon/SPEC.md) — the Coming + Soon page spec. + +[routes]: https://github.com/nsheaps/farish/tree/claude/ai-3d-model-generator-XjoUi/services/api/src/routes +[registry]: https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/services/api/src/routes/index.ts +[coming-soon]: https://github.com/nsheaps/farish/blob/claude/ai-3d-model-generator-XjoUi/apps/web/src/components/ComingSoon.vue diff --git a/docs/monorepo/bun.md b/docs/monorepo/bun.md new file mode 100644 index 0000000..4704f9b --- /dev/null +++ b/docs/monorepo/bun.md @@ -0,0 +1,103 @@ +# bun + TypeScript + +[bun][bun] is farish's JavaScript runtime, package manager, and test runner. +This document is the spec for the bun workspace and the shared TypeScript +configuration. + +[bun]: https://bun.sh + +## Why bun + +- **One tool, three jobs.** bun replaces `npm`/`yarn` (package manager), + `node` (runtime), and `jest`/`vitest` (test runner) — fewer moving parts.[^bun-docs] +- **Native TypeScript.** bun runs `.ts` files directly with no build step, + which keeps local DX fast.[^bun-ts] +- **Workspaces.** bun supports npm-style workspaces, which nx reads to build the + project graph.[^bun-workspaces] + +## Workspace layout + +The root `package.json` declares the workspace globs: + +```json +{ + "workspaces": ["lib/*", "packages/*", "apps/*", "services/*"] +} +``` + +Each matching directory with a `package.json` is a workspace package. Packages +depend on each other with the `workspace:*` protocol: + +```json +{ "dependencies": { "@farish/core": "workspace:*" } } +``` + +`bun install` symlinks `workspace:*` dependencies into `node_modules`, and nx +reads them to order the task graph (see [nx.md](./nx.md)). + +## TypeScript configuration + +There are two root tsconfig files plus one per package: + +| File | Role | +| --------------------- | ---------------------------------------------------------- | +| `tsconfig.base.json` | Shared strict compiler options. Every package extends it. | +| `tsconfig.json` | Solution file — `references` every package for `tsc -b`. | +| `/tsconfig.json` | Extends the base; sets `outDir`, `rootDir`, `composite`. | + +### Strictness — the spec + +`tsconfig.base.json` enables full strictness. Notable flags: + +| Flag | Effect | +| --------------------------------- | ------------------------------------------------- | +| `strict` | All strict-family checks on. | +| `noUncheckedIndexedAccess` | Index access yields `T \| undefined`. | +| `noImplicitReturns` | Every code path must return. | +| `noUnusedLocals` / `Parameters` | Dead bindings are errors. | +| `exactOptionalPropertyTypes` | `?:` and `\| undefined` are distinct. | +| `verbatimModuleSyntax` | Import/export elision is explicit (`import type`).| +| `rewriteRelativeImportExtensions` | Source imports `./x.ts`; `tsc` emits `./x.js`. | + +### The `.ts` import-extension choice + +bun runs `import './handler.ts'` natively, but plain `tsc` rejects a `.ts` +extension when emitting. farish enables `allowImportingTsExtensions` + +`rewriteRelativeImportExtensions` (TypeScript 5.7+) so source files use the +bun-native `.ts` form **and** `tsc` rewrites them to `.js` in `dist/`. One import +style works for both the runtime and the build.[^ts-rewrite] + +## Per-package run-scripts + +Every package defines the standard scripts (initial prompt step 22): + +```json +{ + "scripts": { + "lint": "biome lint ./src", + "format": "biome check --write ./src", + "test": "bun test", + "build": "tsc -p tsconfig.json" + } +} +``` + +`build` runs `tsc` (type-check + emit `dist/`). `test` runs `bun test` (bun's +built-in Jest-compatible runner). Lint/format go through Biome +(see [lint-format.md](./lint-format.md)). + +## Common commands + +```sh +bun install # install workspace dependencies +bun test # run tests in the current package +bun run + + + +${body} + + +`; +} + +/** Render every docs/**.md file into /docs/**.html. Returns the list of + * generated doc pages (repo-relative .md paths) for the index. */ +async function renderDocs(out: string): Promise { + const docsRoot = join(ROOT, 'docs'); + const files = (await walk(docsRoot)).filter((f) => f.endsWith('.md')); + const rendered: string[] = []; + for (const file of files) { + const rel = relative(docsRoot, file); // e.g. pages/home/SPEC.md + const outPath = join(out, 'docs', rel.replace(/\.md$/, '.html')); + await mkdir(dirname(outPath), { recursive: true }); + const md = await readFile(file, 'utf8'); + // Depth of this page below _site/docs/ — `pages/home/SPEC.md` is depth 2. + const depth = rel.split('/').length - 1; + const docsHomeHref = `${'../'.repeat(depth)}index.html`; // → _site/docs/index.html + const appHref = '../'.repeat(depth + 1); // → _site/ (one extra level above docs/) + const body = await marked.parse(md); + await writeFile(outPath, htmlShell(rel, body, docsHomeHref, appHref)); + rendered.push(rel); + } + return rendered.sort(); +} + +/** Write /docs/index.html listing every rendered doc. */ +async function writeDocsIndex(out: string, docs: string[]): Promise { + const items = docs + .map((d) => `
  • ${d}
  • `) + .join('\n'); + const body = `

    farish documentation

    +

    Specs, wireframes, API designs and research for the farish project. +Published automatically on every push to main (initial prompt step 28).

    +
      +${items} +
    `; + // The docs index lives at _site/docs/index.html: it links to itself and to + // the site root one level up. + await writeFile( + join(out, 'docs', 'index.html'), + htmlShell('Documentation', body, 'index.html', '../'), + ); +} + +/** Copy committed screenshot runs and write a run index. */ +async function buildScreenshots(out: string): Promise { + const src = join(ROOT, 'docs', 'screenshots'); + const dest = join(out, 'screenshots'); + await mkdir(dest, { recursive: true }); + let runs: string[] = []; + if (existsSync(src)) { + await cp(src, dest, { recursive: true }); + runs = (await readdir(src, { withFileTypes: true })) + .filter((e) => e.isDirectory()) + .map((e) => e.name) + .sort() + .reverse(); // newest first + } + const items = + runs.length > 0 + ? runs.map((r) => `
  • ${r}
  • `).join('\n') + : '
  • No screenshot runs published yet.
  • '; + const body = `

    farish page screenshots

    +

    Each run is a date-stamped capture of every page, taken by the CI Playwright +suite on a push to main (initial prompt step 28).

    +
      +${items} +
    `; + // The screenshots index lives at _site/screenshots/index.html: docs home is + // one level up then into docs/; the site root is one level up. + await writeFile( + join(dest, 'index.html'), + htmlShell('Screenshots', body, '../docs/index.html', '../'), + ); +} + +/** Copy the built Vue app into the site root. */ +async function copyApp(out: string): Promise { + const dist = join(ROOT, 'apps', 'web', 'dist'); + if (!existsSync(dist)) { + throw new Error(`apps/web/dist not found — run \`nx run web:build\` first.`); + } + await cp(dist, out, { recursive: true }); +} + +async function main(): Promise { + const outArg = process.argv[2]; + if (!outArg) { + process.stderr.write('usage: bun run scripts/build-pages-site.ts \n'); + process.exit(1); + } + const out = resolve(outArg); + await mkdir(out, { recursive: true }); + await copyApp(out); + const docs = await renderDocs(out); + await writeDocsIndex(out, docs); + await buildScreenshots(out); + process.stdout.write( + `Pages site assembled at ${out} — app + ${docs.length} doc pages + screenshots.\n`, + ); +} + +main().catch((err) => { + process.stderr.write(`build-pages-site failed: ${(err as Error).message}\n`); + process.exit(1); +}); diff --git a/scripts/publish-screenshots.ts b/scripts/publish-screenshots.ts new file mode 100644 index 0000000..eb1697e --- /dev/null +++ b/scripts/publish-screenshots.ts @@ -0,0 +1,98 @@ +#!/usr/bin/env bun +import { existsSync } from 'node:fs'; +/** + * publish-screenshots.ts — copy a Playwright screenshot run onto a + * date-based path under docs/screenshots/. + * + * Initial prompt step 28 requires that CI, on every push to `main`, commits + * the captured page screenshots back to the repo under a date-based path so + * progress can be tracked as a visual changelog. This script does the copy + + * per-run index; the CI workflow (`.github/workflows/pages.yml`) does the + * `git commit` with the loop-safe `[skip ci]` message. + * + * It is also runnable locally so a developer can reproduce exactly what CI + * does (initial prompt step 24 — CI and local share codepaths): + * + * cd apps/web && bun run e2e # produces e2e/output/screenshots/*.png + * bun run scripts/publish-screenshots.ts + * + * Source : apps/web/e2e/output/screenshots/ (raw PNGs from the suite) + * Dest : docs/screenshots/-/ + * + * The destination directory name is the UTC date plus a caller-supplied run + * id (the GitHub Actions run id in CI, or any token locally). Each run path + * is immutable — the script refuses to overwrite an existing run directory. + * + * Docs: scripts/README.md + */ +import { cp, mkdir, readdir, writeFile } from 'node:fs/promises'; +import { dirname, join, resolve } from 'node:path'; + +/** Repo root — this file lives in /scripts/. */ +const ROOT = resolve(dirname(new URL(import.meta.url).pathname), '..'); + +/** Raw screenshot output from the Playwright suite. */ +const SRC = join(ROOT, 'apps', 'web', 'e2e', 'output', 'screenshots'); + +/** Committed-screenshots root. */ +const DEST_ROOT = join(ROOT, 'docs', 'screenshots'); + +/** UTC date stamp `YYYY-MM-DD` for the run directory name. */ +function utcDate(): string { + return new Date().toISOString().slice(0, 10); +} + +async function main(): Promise { + const runId = process.argv[2]; + if (!runId) { + process.stderr.write( + 'usage: bun run scripts/publish-screenshots.ts \n' + + ' — CI run id (GitHub Actions) or any local token.\n', + ); + process.exit(1); + } + + if (!existsSync(SRC)) { + process.stderr.write( + `no screenshots found at ${SRC} — run the e2e suite first ` + + '(cd apps/web && bun run e2e).\n', + ); + process.exit(1); + } + + const pngs = (await readdir(SRC)).filter((f) => f.endsWith('.png')).sort(); + if (pngs.length === 0) { + process.stderr.write(`no .png files in ${SRC} — nothing to publish.\n`); + process.exit(1); + } + + const runName = `${utcDate()}-${runId}`; + const dest = join(DEST_ROOT, runName); + if (existsSync(dest)) { + process.stderr.write(`run directory already exists: ${dest} — refusing to overwrite.\n`); + process.exit(1); + } + + await mkdir(dest, { recursive: true }); + await cp(SRC, dest, { recursive: true }); + + // Per-run README so each run directory is self-describing on GitHub. + const list = pngs.map((p) => `- \`${p}\``).join('\n'); + const readme = `# Screenshot run ${runName} + +Captured by the CI Playwright suite (initial prompt step 28). + +Pages in this run: + +${list} +`; + await writeFile(join(dest, 'README.md'), readme); + + // Emit the run path on stdout so the CI workflow can capture it. + process.stdout.write(`${runName}\n`); +} + +main().catch((err) => { + process.stderr.write(`publish-screenshots failed: ${(err as Error).message}\n`); + process.exit(1); +}); diff --git a/services/api/Dockerfile b/services/api/Dockerfile new file mode 100644 index 0000000..2c4e1d8 --- /dev/null +++ b/services/api/Dockerfile @@ -0,0 +1,43 @@ +# Dockerfile — container image for the @farish/api service. +# +# This image is the artifact PUBLISHED to ghcr.io (initial prompt step 26). +# It is NOT used for local development: Tilt runs the API as a native process +# (see the repo Tiltfile and docs/monorepo/tilt.md). The image exists so the +# API server has a deployable form once a hosting target (e.g. Vercel per +# amendment A3, or any container host) is wired up in a later step. +# +# Build context is the repo root so the bun workspace + the libraries the API +# imports (@farish/api-contract, @farish/mock-data) are all available: +# +# docker build -f services/api/Dockerfile -t farish-api . +# +# See infra/ghcr.md for the full publish flow. + +# --- build stage -------------------------------------------------------------- +FROM oven/bun:1.3.11 AS build +WORKDIR /repo + +# Install workspace dependencies against the committed lockfile. +COPY package.json bun.lock ./ +COPY lib/api-contract/package.json lib/api-contract/ +COPY lib/mock-data/package.json lib/mock-data/ +COPY services/api/package.json services/api/ +RUN bun install --frozen-lockfile + +# Copy the sources the API needs and compile them. +COPY tsconfig.base.json ./ +COPY lib/api-contract lib/api-contract +COPY lib/mock-data lib/mock-data +COPY services/api services/api +RUN cd lib/api-contract && bun run build \ + && cd ../mock-data && bun run build \ + && cd ../../services/api && bun run build + +# --- runtime stage ------------------------------------------------------------ +FROM oven/bun:1.3.11-slim AS runtime +WORKDIR /app +ENV PORT=8787 +COPY --from=build /repo /repo +WORKDIR /repo/services/api +EXPOSE 8787 +CMD ["bun", "run", "start"] diff --git a/services/api/package.json b/services/api/package.json new file mode 100644 index 0000000..8d35a36 --- /dev/null +++ b/services/api/package.json @@ -0,0 +1,20 @@ +{ + "name": "@farish/api", + "version": "0.0.0", + "private": true, + "description": "farish API server — microservice-architecture HTTP framework. In development a single process serves every route; in production each route group can deploy as its own service.", + "type": "module", + "scripts": { + "lint": "biome lint ./src", + "format": "biome check --write ./src", + "test": "bun test", + "build": "tsc -p tsconfig.json", + "start": "bun run src/server.ts", + "dev": "bun run --watch src/server.ts", + "release": ".mise/tasks/publish-api.sh" + }, + "dependencies": { + "@farish/api-contract": "workspace:*", + "@farish/mock-data": "workspace:*" + } +} diff --git a/services/api/project.json b/services/api/project.json new file mode 100644 index 0000000..6bb56de --- /dev/null +++ b/services/api/project.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "api", + "projectType": "application", + "sourceRoot": "services/api/src", + "tags": ["scope:service"], + "targets": { + "lint": { "executor": "nx:run-script", "options": { "script": "lint" } }, + "format": { "executor": "nx:run-script", "options": { "script": "format" } }, + "test": { "executor": "nx:run-script", "options": { "script": "test" } }, + "build": { "executor": "nx:run-script", "options": { "script": "build" } } + } +} diff --git a/services/api/src/router.test.ts b/services/api/src/router.test.ts new file mode 100644 index 0000000..8fc8bb1 --- /dev/null +++ b/services/api/src/router.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, test } from 'bun:test'; +import { createRouter, errorResponse, type Route } from './router.ts'; + +const okRoute: Route = { + method: 'GET', + path: '/ok', + handler: () => ({ ok: true }), +}; + +const boomRoute: Route = { + method: 'GET', + path: '/boom', + handler: () => { + throw new Error('handler exploded'); + }, +}; + +const router = createRouter([okRoute, boomRoute]); + +describe('@farish/api — router', () => { + test('matched route returns its JSON payload', async () => { + const res = await router(new Request('http://localhost/ok')); + expect(res.status).toBe(200); + expect(res.headers.get('content-type')).toBe('application/json'); + expect(await res.json()).toEqual({ ok: true }); + }); + + test('unknown route returns 404 not_found', async () => { + const res = await router(new Request('http://localhost/missing')); + expect(res.status).toBe(404); + expect(await res.json()).toEqual({ error: 'not_found' }); + }); + + test('wrong method on a known path returns 404', async () => { + const res = await router( + new Request('http://localhost/ok', { method: 'POST' }), + ); + expect(res.status).toBe(404); + }); + + test('a throwing handler becomes 500 internal_error', async () => { + const res = await router(new Request('http://localhost/boom')); + expect(res.status).toBe(500); + expect(await res.json()).toEqual({ error: 'internal_error' }); + }); + + test('OPTIONS preflight returns 204 with CORS headers', async () => { + const res = await router( + new Request('http://localhost/ok', { method: 'OPTIONS' }), + ); + expect(res.status).toBe(204); + expect(res.headers.get('access-control-allow-origin')).toBe('*'); + }); + + test('every response carries the CORS origin header', async () => { + const res = await router(new Request('http://localhost/ok')); + expect(res.headers.get('access-control-allow-origin')).toBe('*'); + }); + + test('errorResponse builds the standard error body', async () => { + const res = errorResponse('teapot', 418); + expect(res.status).toBe(418); + expect(await res.json()).toEqual({ error: 'teapot' }); + }); +}); diff --git a/services/api/src/router.ts b/services/api/src/router.ts new file mode 100644 index 0000000..a599984 --- /dev/null +++ b/services/api/src/router.ts @@ -0,0 +1,163 @@ +/** + * @farish/api — the minimal routing framework. + * + * Microservice architecture, single-process dev: every route is an + * independent {@link Route} module. `createRouter` composes a list of routes + * into one `fetch` handler. In development one server mounts every route; in + * production a route group could be split into its own deployable service + * without changing any route module — only which routes a server mounts. + * + * The router centralises five concerns every route needs: + * - JSON serialisation + * - CORS headers (the browser app runs on a different dev port) + * - Path-parameter extraction (`:param` segments in route patterns) + * - Auth-token extraction (shared by routes that require authentication) + * - Uncaught-error handling (a thrown handler becomes a 500 `internal_error` + * instead of crashing the process) + */ +import type { ApiError } from '@farish/api-contract'; + +/** HTTP methods the router recognises. */ +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + +/** + * A route handler. + * + * Receives the request and the extracted path params. Returns any + * JSON-serialisable value or a `Response` directly (e.g. `noContent()`). + * Handlers may safely ignore either argument if they do not need it. + */ +export type RouteHandler = ( + req: Request, + params: Record, +) => unknown | Promise; + +/** A single mountable route. */ +export interface Route { + /** HTTP method this route answers. */ + method: HttpMethod; + /** + * URL path pattern. Supports `:param` segments, e.g. `/models/:id`. + * Exact paths (no `:` segments) are also valid. + */ + path: string; + /** The handler producing the response payload. */ + handler: RouteHandler; +} + +/** CORS headers applied to every response so the browser app can call the API. */ +const CORS_HEADERS: Record = { + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS', + 'access-control-allow-headers': 'content-type,authorization', +}; + +/** Serialise a payload to a JSON `Response` with CORS headers. */ +function json(payload: unknown, status = 200): Response { + return new Response(JSON.stringify(payload), { + status, + headers: { 'content-type': 'application/json', ...CORS_HEADERS }, + }); +} + +/** Build a standard {@link ApiError} JSON response. */ +export function errorResponse(code: string, status: number): Response { + const body: ApiError = { error: code }; + return json(body, status); +} + +/** + * Return a `204 No Content` response with CORS headers. + * + * Used by fire-and-forget endpoints like `POST /models/:id/views`. + */ +export function noContent(): Response { + return new Response(null, { status: 204, headers: CORS_HEADERS }); +} + +/** + * Extract the bearer token from the `Authorization` header. + * + * Returns the raw token string when the header is present and starts with + * `Bearer `, or `null` otherwise. Does NOT validate the token — that is the + * route's responsibility. + */ +export function extractBearerToken(req: Request): string | null { + const auth = req.headers.get('authorization'); + if (auth === null || !auth.startsWith('Bearer ')) return null; + const token = auth.slice(7).trim(); + return token.length > 0 ? token : null; +} + +/** + * Match a URL pathname against a route pattern. + * + * Converts `:param` segments to capture groups. Returns a `Record` of captured values when the pathname matches, or `null` when it + * does not. + * + * @example + * matchPath('/models/:id', '/models/abc-123') // → { id: 'abc-123' } + * matchPath('/models/:id', '/models') // → null + */ +function matchPath(pattern: string, pathname: string): Record | null { + const keys: string[] = []; + const regexSrc = pattern.replace(/:([^/]+)/g, (_: string, key: string) => { + keys.push(key); + return '([^/]+)'; + }); + const m = pathname.match(new RegExp(`^${regexSrc}$`)); + if (m === null) return null; + return Object.fromEntries( + keys.map((k, i) => [k, decodeURIComponent(m[i + 1] ?? '')] as [string, string]), + ); +} + +/** + * Compose routes into a single `fetch` handler suitable for `Bun.serve`. + * + * Resolution order: CORS preflight → method + path pattern match → 404. + * A handler that throws is caught and returned as `500 internal_error`. + * A handler that returns a `Response` directly is forwarded as-is (e.g. + * `noContent()`, `errorResponse()`). + */ +export function createRouter(routes: readonly Route[]): (req: Request) => Promise { + return async (req: Request): Promise => { + // CORS preflight — answer OPTIONS without hitting a route. + if (req.method === 'OPTIONS') { + return new Response(null, { status: 204, headers: CORS_HEADERS }); + } + + const { pathname } = new URL(req.url); + + // Find the first route whose method and path pattern both match. + let matchedRoute: Route | undefined; + let matchedParams: Record = {}; + + for (const route of routes) { + if (route.method !== req.method) continue; + const params = matchPath(route.path, pathname); + if (params !== null) { + matchedRoute = route; + matchedParams = params; + break; + } + } + + if (matchedRoute === undefined) { + return errorResponse('not_found', 404); + } + + try { + const payload = await matchedRoute.handler(req, matchedParams); + // If the handler already built a Response (e.g. noContent, errorResponse), + // pass it through without re-wrapping. + if (payload instanceof Response) return payload; + return json(payload); + } catch (cause) { + // Surface the cause on the error channel; never crash the process. + console.error('[api] unhandled route error', cause); + return errorResponse('internal_error', 500); + } + }; +} diff --git a/services/api/src/routes/create-model.ts b/services/api/src/routes/create-model.ts new file mode 100644 index 0000000..7120e57 --- /dev/null +++ b/services/api/src/routes/create-model.ts @@ -0,0 +1,51 @@ +/** + * @farish/api — `POST /models` endpoint stub. + * + * Publishes a locally generated model to the shared backend, making it + * discoverable via Explore, Leaderboards, and Profile. + * + * This is a STUB: it validates that an `Authorization` header is present, + * then returns a deterministic mock 201 Created response. Real data storage + * is implemented in step 35+. + * Spec: `docs/api/create-model/SPEC.md`. + */ +import { type CreateModelResponse, ROUTES } from '@farish/api-contract'; +import { createLorem } from '@farish/mock-data'; +import { errorResponse, extractBearerToken } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** Build a stub {@link CreateModelResponse} (201 Created) payload. */ +export function createModelPayload(): CreateModelResponse { + const lorem = createLorem(Date.now() % 1000); + const id = `mock-new-${String(Date.now() % 100000)}`; + return { + id, + title: lorem.title(3), + geometryUrl: `https://mock.farish.dev/geometry/${id}.glb`, + thumbnailUrl: null, + createdAt: new Date().toISOString(), + }; +} + +/** The mountable create-model route. */ +export const createModelRoute: Route = { + method: 'POST', + path: ROUTES.createModel, + handler: (req) => { + // Auth is required — reject unauthenticated requests with 401. + const token = extractBearerToken(req); + if (token === null) { + return errorResponse('unauthorized', 401); + } + // Stub: return a mock 201 response; real body parsing is step 35+. + return new Response(JSON.stringify(createModelPayload()), { + status: 201, + headers: { + 'content-type': 'application/json', + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS', + 'access-control-allow-headers': 'content-type,authorization', + }, + }); + }, +}; diff --git a/services/api/src/routes/get-leaderboard.ts b/services/api/src/routes/get-leaderboard.ts new file mode 100644 index 0000000..9efd39c --- /dev/null +++ b/services/api/src/routes/get-leaderboard.ts @@ -0,0 +1,94 @@ +/** + * @farish/api — `GET /leaderboard` endpoint stub. + * + * Returns an ordered list of up to 50 ranked models for one of three boards + * (Best Rated, Most Rated / Popular, Most Viewed) filtered to a chosen time + * bucket (1 week, 1 month, 1 year, or all time). + * + * This is a STUB: it validates `board` and `period`, then returns + * deterministic mock entries. Real ranking logic is implemented in step 35+. + * Spec: `docs/api/get-leaderboard/SPEC.md`. + */ +import { + type GetLeaderboardResponse, + type LeaderboardBoard, + type LeaderboardItem, + type LeaderboardPeriod, + ROUTES, +} from '@farish/api-contract'; +import { mockModelCards } from '@farish/mock-data'; +import { errorResponse } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** Valid board values per spec. */ +const VALID_BOARDS = new Set(['rated', 'popular', 'viewed']); +/** Valid period values per spec. */ +const VALID_PERIODS = new Set(['1w', '1m', '1y', 'all']); +/** Maximum and default result count per spec. */ +const MAX_LIMIT = 50; +const DEFAULT_LIMIT = 50; + +/** + * Build the stub {@link GetLeaderboardResponse} payload from query params. + * + * Produces plausible metric values based on the board type using mock cards. + */ +export function getLeaderboardPayload( + board: LeaderboardBoard, + period: LeaderboardPeriod, + limit: number, +): GetLeaderboardResponse { + const cards = mockModelCards(limit, /* seed */ 42); + const items: LeaderboardItem[] = cards.map((card, index) => { + // Derive a board-appropriate metric value for each card. + let metricValue: number; + if (board === 'rated') { + metricValue = Math.round((5 - index * 0.05) * 100) / 100; + } else if (board === 'popular') { + metricValue = Math.max(1, 500 - index * 10); + } else { + // viewed + metricValue = Math.max(1, 10000 - index * 200); + } + return { + rank: index + 1, + id: card.id, + title: card.title, + thumbnailUrl: card.thumbnailUrl, + authorName: card.authorName, + authorId: card.authorId, + authorUsername: card.authorUsername, + metricValue, + createdAt: card.createdAt, + }; + }); + return { board, period, items }; +} + +/** The mountable get-leaderboard route. */ +export const getLeaderboardRoute: Route = { + method: 'GET', + path: ROUTES.getLeaderboard, + handler: (_req, _params) => { + const url = new URL(_req.url); + const board = url.searchParams.get('board'); + const period = url.searchParams.get('period'); + const rawLimit = url.searchParams.get('limit'); + + if (board === null || !VALID_BOARDS.has(board)) { + return errorResponse('invalid_board', 400); + } + if (period === null || !VALID_PERIODS.has(period)) { + return errorResponse('invalid_period', 400); + } + + const limit = + rawLimit !== null ? Math.min(Number(rawLimit), MAX_LIMIT) : DEFAULT_LIMIT; + + return getLeaderboardPayload( + board as LeaderboardBoard, + period as LeaderboardPeriod, + limit, + ); + }, +}; diff --git a/services/api/src/routes/get-model.ts b/services/api/src/routes/get-model.ts new file mode 100644 index 0000000..2774f84 --- /dev/null +++ b/services/api/src/routes/get-model.ts @@ -0,0 +1,55 @@ +/** + * @farish/api — `GET /models/:id` endpoint stub. + * + * Returns the full record for a single publicly shared model, including its + * prompt, generation params, geometry URL, metadata, and social counters. + * + * This is a STUB: the `id` path param is used as a seed for deterministic + * mock data. Real data access is implemented in step 35+. + * Spec: `docs/api/get-model/SPEC.md`. + */ +import { type GetModelResponse, ROUTES } from '@farish/api-contract'; +import { mockModelDetail } from '@farish/mock-data'; +import { errorResponse } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** + * Build the stub {@link GetModelResponse} payload for a given model ID. + * + * Uses `mockModelDetail` (seeded by the ID) to produce deterministic fields. + */ +export function getModelPayload(id: string): GetModelResponse { + const detail = mockModelDetail(id); + // Derive author subfields from the single `author` string in the mock. + const authorSlug = detail.author.toLowerCase().replace(/\s+/g, '-'); + return { + id: detail.id, + title: detail.title, + prompt: detail.prompt, + params: { + resolution: detail.resolution, + style: detail.style, + complexity: detail.complexity, + }, + geometryUrl: `https://mock.farish.dev/geometry/${id}.glb`, + thumbnailUrl: detail.thumbnailUrl, + authorName: detail.author, + authorId: `mock-author-${authorSlug}`, + authorUsername: authorSlug, + createdAt: `${detail.createdAt}T00:00:00Z`, + viewCount: detail.views, + ratingAvg: detail.rating, + ratingCount: detail.ratingCount, + }; +} + +/** The mountable get-model route. */ +export const getModelRoute: Route = { + method: 'GET', + path: ROUTES.getModel, + handler: (_req, params) => { + const { id } = params; + if (!id) return errorResponse('model_not_found', 404); + return getModelPayload(id); + }, +}; diff --git a/services/api/src/routes/get-user-profile.ts b/services/api/src/routes/get-user-profile.ts new file mode 100644 index 0000000..0bdcac2 --- /dev/null +++ b/services/api/src/routes/get-user-profile.ts @@ -0,0 +1,68 @@ +/** + * @farish/api — `GET /users/:username` endpoint stub. + * + * Returns the public profile record for a creator: display name, avatar, + * join date, and aggregate stats. + * + * This is a STUB: the username is used as a seed for deterministic mock + * data. Real data access is implemented in step 35+. + * Spec: `docs/api/get-user-profile/SPEC.md`. + */ +import { type GetUserProfileResponse, ROUTES } from '@farish/api-contract'; +import { createLorem } from '@farish/mock-data'; +import { errorResponse } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** Join dates used in stub responses — deterministically picked by username seed. */ +const JOIN_DATES: readonly string[] = [ + '2025-06-15', + '2025-08-02', + '2025-09-20', + '2025-11-07', + '2026-01-14', + '2026-02-28', + '2026-03-18', + '2026-04-05', +]; + +/** + * Build a stub {@link GetUserProfileResponse} payload for a given username. + * + * Derives a numeric seed from the username string so the same username + * always returns the same profile data. + */ +export function getUserProfilePayload(username: string): GetUserProfileResponse { + // Derive a stable numeric seed from the username characters. + const seed = + username + .split('') + .reduce((acc, ch) => ((acc << 5) - acc + ch.charCodeAt(0)) | 0, 0) >>> 0; + + const lorem = createLorem(seed); + const index = seed % 8; + const joinDate = JOIN_DATES[index % JOIN_DATES.length] ?? '2025-06-15'; + const modelCount = 5 + (seed % 45); + const totalViews = 500 + (seed % 49500); + const avgRating = index % 6 === 0 ? null : 3 + ((seed % 4) * 0.5); + + return { + username, + displayName: lorem.title(2), + avatarUrl: null, + joinDate: joinDate ?? '2025-06-15', + modelCount, + avgRating, + totalViews, + }; +} + +/** The mountable get-user-profile route. */ +export const getUserProfileRoute: Route = { + method: 'GET', + path: ROUTES.getUserProfile, + handler: (_req, params) => { + const { username } = params; + if (!username) return errorResponse('user_not_found', 404); + return getUserProfilePayload(username); + }, +}; diff --git a/services/api/src/routes/health.ts b/services/api/src/routes/health.ts new file mode 100644 index 0000000..111b077 --- /dev/null +++ b/services/api/src/routes/health.ts @@ -0,0 +1,29 @@ +/** + * @farish/api — health endpoint. + * + * `GET /health` is the liveness/readiness probe. Tilt's `local_resource` + * readiness probe and CI smoke tests hit this route to confirm the API + * process is serving requests. + */ +import { type HealthResponse, ROUTES } from '@farish/api-contract'; +import type { Route } from '../router.ts'; + +/** Process start time — used to report uptime. */ +const START_TIME = Date.now(); + +/** Build the {@link HealthResponse} payload. */ +export function healthPayload(): HealthResponse { + return { + status: 'ok', + service: 'api', + version: '0.0.0', + uptimeSeconds: Math.round((Date.now() - START_TIME) / 1000), + }; +} + +/** The mountable health route. */ +export const healthRoute: Route = { + method: 'GET', + path: ROUTES.health, + handler: () => healthPayload(), +}; diff --git a/services/api/src/routes/index.ts b/services/api/src/routes/index.ts new file mode 100644 index 0000000..e90f2de --- /dev/null +++ b/services/api/src/routes/index.ts @@ -0,0 +1,40 @@ +/** + * @farish/api — the route registry. + * + * The single list of routes the development server mounts. Adding an endpoint + * means writing a route module and appending it here. When the API is later + * split into separate microservices, each service mounts the subset of this + * list it owns. + * + * Route ordering matters for path-param routes that share a path prefix: + * more-specific patterns (e.g. `/users/:username/models`) must appear before + * less-specific ones (e.g. `/users/:username`) so the router matches the + * correct handler first. + */ +import type { Route } from '../router.ts'; +import { createModelRoute } from './create-model.ts'; +import { getLeaderboardRoute } from './get-leaderboard.ts'; +import { getModelRoute } from './get-model.ts'; +import { getUserProfileRoute } from './get-user-profile.ts'; +import { healthRoute } from './health.ts'; +import { listModelsRoute } from './list-models.ts'; +import { listUserModelsRoute } from './list-user-models.ts'; +import { recordViewRoute } from './record-view.ts'; +import { submitRatingRoute } from './submit-rating.ts'; + +/** Every route the dev server serves. */ +export const routes: readonly Route[] = [ + // Infrastructure + healthRoute, + // Models — more-specific sub-resource paths before the base /models path. + recordViewRoute, // POST /models/:id/views + submitRatingRoute, // PUT /models/:id/rating + getModelRoute, // GET /models/:id + listModelsRoute, // GET /models + createModelRoute, // POST /models + // Leaderboard + getLeaderboardRoute, // GET /leaderboard + // Users — more-specific /models sub-path before the profile path. + listUserModelsRoute, // GET /users/:username/models + getUserProfileRoute, // GET /users/:username +]; diff --git a/services/api/src/routes/list-models.ts b/services/api/src/routes/list-models.ts new file mode 100644 index 0000000..92f6cd6 --- /dev/null +++ b/services/api/src/routes/list-models.ts @@ -0,0 +1,58 @@ +/** + * @farish/api — `GET /models` endpoint stub. + * + * Returns a paginated, optionally filtered list of publicly shared models. + * This is a STUB: it serves mock data, not a real model store. The full + * implementation (real data via a model abstraction, search, auth) is + * specified in `docs/api/list-models/SPEC.md` and implemented in later + * prompt steps (35+). + * + * Query params handled in the stub: + * - `page` — 1-based page number (default 1) + * - `limit` — results per page (default 24, max 100) + * - `sort` — validated but not applied (stub always returns the same data) + * - `q` — accepted but not applied (stub does not search) + * - `filter`— accepted but not applied + */ +import { type ListModelsResponse, ROUTES } from '@farish/api-contract'; +import { mockModelCards } from '@farish/mock-data'; +import { errorResponse } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** Total number of mock models in the fake dataset. */ +const STUB_TOTAL = 48; + +/** Valid sort values per spec. */ +const VALID_SORT = new Set(['newest', 'rating', 'popular', 'views']); + +/** Maximum allowed limit per spec. */ +const MAX_LIMIT = 100; + +/** Build the stub {@link ListModelsResponse} payload from query params. */ +export function listModelsPayload(url: URL): ListModelsResponse | Response { + const sort = url.searchParams.get('sort') ?? 'newest'; + if (!VALID_SORT.has(sort)) { + return errorResponse('invalid_sort', 400); + } + + const rawLimit = url.searchParams.get('limit'); + const limit = rawLimit !== null ? Number(rawLimit) : 24; + if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) { + return errorResponse('limit_too_large', 400); + } + + const rawPage = url.searchParams.get('page'); + const page = rawPage !== null ? Number(rawPage) : 1; + + const totalPages = Math.ceil(STUB_TOTAL / limit); + const items = mockModelCards(Math.min(limit, STUB_TOTAL)); + + return { items, total: STUB_TOTAL, page, totalPages }; +} + +/** The mountable list-models route. */ +export const listModelsRoute: Route = { + method: 'GET', + path: ROUTES.listModels, + handler: (req) => listModelsPayload(new URL(req.url)), +}; diff --git a/services/api/src/routes/list-user-models.ts b/services/api/src/routes/list-user-models.ts new file mode 100644 index 0000000..61130b6 --- /dev/null +++ b/services/api/src/routes/list-user-models.ts @@ -0,0 +1,72 @@ +/** + * @farish/api — `GET /users/:username/models` endpoint stub. + * + * Returns a paginated list of publicly shared models authored by a specific + * creator. Used by the Profile page to populate the ModelGrid. + * + * This is a STUB: the username is used as a seed for deterministic mock + * data. Real data access is implemented in step 35+. + * Spec: `docs/api/list-user-models/SPEC.md`. + */ +import { type ListUserModelsResponse, type UserModelCard, ROUTES } from '@farish/api-contract'; +import { mockModelCards } from '@farish/mock-data'; +import { errorResponse } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** Total stub models per user. */ +const STUB_USER_TOTAL = 18; +/** Maximum allowed limit per spec. */ +const MAX_LIMIT = 100; + +/** + * Build the stub {@link ListUserModelsResponse} payload for a given username. + * + * Derives a seed from the username so each user always returns the same data. + */ +export function listUserModelsPayload( + username: string, + page: number, + limit: number, +): ListUserModelsResponse { + // Derive a numeric seed from the username for deterministic cards. + const seed = + username + .split('') + .reduce((acc, ch) => ((acc << 5) - acc + ch.charCodeAt(0)) | 0, 0) >>> 0; + + const totalPages = Math.ceil(STUB_USER_TOTAL / limit); + const cards = mockModelCards(Math.min(limit, STUB_USER_TOTAL), seed % 1000); + + const items: UserModelCard[] = cards.map((card) => ({ + id: card.id, + title: card.title, + thumbnailUrl: card.thumbnailUrl, + ratingAvg: card.ratingAvg, + viewCount: card.viewCount, + createdAt: card.createdAt, + })); + + return { items, total: STUB_USER_TOTAL, page, totalPages }; +} + +/** The mountable list-user-models route. */ +export const listUserModelsRoute: Route = { + method: 'GET', + path: ROUTES.listUserModels, + handler: (req, params) => { + const { username } = params; + if (!username) return errorResponse('user_not_found', 404); + + const url = new URL(req.url); + const rawLimit = url.searchParams.get('limit'); + const limit = rawLimit !== null ? Number(rawLimit) : 24; + if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) { + return errorResponse('limit_too_large', 400); + } + + const rawPage = url.searchParams.get('page'); + const page = rawPage !== null ? Number(rawPage) : 1; + + return listUserModelsPayload(username, page, limit); + }, +}; diff --git a/services/api/src/routes/record-view.ts b/services/api/src/routes/record-view.ts new file mode 100644 index 0000000..70cfb3f --- /dev/null +++ b/services/api/src/routes/record-view.ts @@ -0,0 +1,29 @@ +/** + * @farish/api — `POST /models/:id/views` endpoint stub. + * + * Increments the view counter for a shared model. Fire-and-forget: the + * client ignores the response body. Auth is optional — deduplication uses + * user identity when available, IP+time-window otherwise. + * + * This is a STUB: it returns `204 No Content` after confirming the model ID + * is non-empty. Real deduplication logic is implemented in step 35+. + * Spec: `docs/api/record-view/SPEC.md`. + */ +import { ROUTES } from '@farish/api-contract'; +import { errorResponse, noContent } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** The mountable record-view route. */ +export const recordViewRoute: Route = { + method: 'POST', + path: ROUTES.recordView, + handler: (_req, params) => { + const { id } = params; + // Stub validation: a missing ID means the model path didn't match — + // treat as not found. (In practice the router only calls this handler + // when `:id` captured a non-empty segment.) + if (!id) return errorResponse('model_not_found', 404); + // Stub: silently succeed — real dedup logic is step 35+. + return noContent(); + }, +}; diff --git a/services/api/src/routes/routes.test.ts b/services/api/src/routes/routes.test.ts new file mode 100644 index 0000000..c8bdbb6 --- /dev/null +++ b/services/api/src/routes/routes.test.ts @@ -0,0 +1,388 @@ +/** + * @farish/api — unit tests for every route module. + * + * Each test suite covers: + * 1. The route descriptor (method + path) + * 2. Payload / handler output shape matches the contract spec + * 3. Error paths (invalid params, missing auth) + * + * These are unit tests — they exercise the payload builder functions and + * handler calls in isolation, without starting a real HTTP server. + */ +import { describe, expect, test } from 'bun:test'; +import { createModelRoute } from './create-model.ts'; +import { getLeaderboardPayload, getLeaderboardRoute } from './get-leaderboard.ts'; +import { getModelPayload, getModelRoute } from './get-model.ts'; +import { getUserProfilePayload, getUserProfileRoute } from './get-user-profile.ts'; +import { healthPayload, healthRoute } from './health.ts'; +import { listModelsPayload, listModelsRoute } from './list-models.ts'; +import { listUserModelsPayload, listUserModelsRoute } from './list-user-models.ts'; +import { recordViewRoute } from './record-view.ts'; +import { submitRatingPayload, submitRatingRoute } from './submit-rating.ts'; + +// --------------------------------------------------------------------------- +// Health +// --------------------------------------------------------------------------- + +describe('@farish/api — health route', () => { + test('is GET /health', () => { + expect(healthRoute.method).toBe('GET'); + expect(healthRoute.path).toBe('/health'); + }); + + test('payload reports ok status and the service name', () => { + const payload = healthPayload(); + expect(payload.status).toBe('ok'); + expect(payload.service).toBe('api'); + expect(payload.uptimeSeconds).toBeGreaterThanOrEqual(0); + }); +}); + +// --------------------------------------------------------------------------- +// List Models — GET /models +// --------------------------------------------------------------------------- + +describe('@farish/api — list-models route', () => { + test('is GET /models', () => { + expect(listModelsRoute.method).toBe('GET'); + expect(listModelsRoute.path).toBe('/models'); + }); + + test('payload returns items array with pagination fields', () => { + const result = listModelsPayload(new URL('http://localhost/models')); + // Should not be an error Response + expect(result instanceof Response).toBe(false); + const payload = result as { items: unknown[]; total: number; page: number; totalPages: number }; + expect(Array.isArray(payload.items)).toBe(true); + expect(payload.items.length).toBeGreaterThan(0); + expect(typeof payload.total).toBe('number'); + expect(typeof payload.page).toBe('number'); + expect(typeof payload.totalPages).toBe('number'); + expect(payload.total).toBeGreaterThanOrEqual(payload.items.length); + }); + + test('every item has spec-required fields', () => { + const result = listModelsPayload(new URL('http://localhost/models')); + const payload = result as { items: Record[] }; + for (const item of payload.items) { + expect(typeof item.id).toBe('string'); + expect(typeof item.title).toBe('string'); + expect(typeof item.authorName).toBe('string'); + expect(typeof item.authorId).toBe('string'); + expect(typeof item.authorUsername).toBe('string'); + expect(typeof item.viewCount).toBe('number'); + expect(typeof item.createdAt).toBe('string'); + // thumbnailUrl and ratingAvg may be null + } + }); + + test('invalid sort returns 400 error Response', () => { + const result = listModelsPayload(new URL('http://localhost/models?sort=invalid')); + expect(result instanceof Response).toBe(true); + expect((result as Response).status).toBe(400); + }); + + test('limit exceeding max returns 400 error Response', () => { + const result = listModelsPayload(new URL('http://localhost/models?limit=999')); + expect(result instanceof Response).toBe(true); + expect((result as Response).status).toBe(400); + }); + + test('custom limit and page are reflected in the response', () => { + const result = listModelsPayload(new URL('http://localhost/models?limit=10&page=2')); + const payload = result as { items: unknown[]; page: number; totalPages: number }; + expect(payload.page).toBe(2); + expect(payload.items.length).toBeLessThanOrEqual(10); + }); +}); + +// --------------------------------------------------------------------------- +// Get Model — GET /models/:id +// --------------------------------------------------------------------------- + +describe('@farish/api — get-model route', () => { + test('is GET /models/:id', () => { + expect(getModelRoute.method).toBe('GET'); + expect(getModelRoute.path).toBe('/models/:id'); + }); + + test('payload returns all spec-required fields', () => { + const payload = getModelPayload('test-id-abc'); + expect(typeof payload.id).toBe('string'); + expect(typeof payload.title).toBe('string'); + expect(typeof payload.prompt).toBe('string'); + expect(typeof payload.params).toBe('object'); + expect(typeof payload.params.resolution).toBe('number'); + expect(typeof payload.params.style).toBe('string'); + expect(typeof payload.params.complexity).toBe('number'); + expect(typeof payload.geometryUrl).toBe('string'); + expect(typeof payload.authorName).toBe('string'); + expect(typeof payload.authorId).toBe('string'); + expect(typeof payload.authorUsername).toBe('string'); + expect(typeof payload.createdAt).toBe('string'); + expect(typeof payload.viewCount).toBe('number'); + expect(typeof payload.ratingCount).toBe('number'); + // ratingAvg may be null or number + expect(payload.ratingAvg === null || typeof payload.ratingAvg === 'number').toBe(true); + }); + + test('same id always returns the same data (deterministic)', () => { + const a = getModelPayload('determinism-test'); + const b = getModelPayload('determinism-test'); + expect(a.title).toBe(b.title); + expect(a.viewCount).toBe(b.viewCount); + }); +}); + +// --------------------------------------------------------------------------- +// Create Model — POST /models +// --------------------------------------------------------------------------- + +describe('@farish/api — create-model route', () => { + test('is POST /models', () => { + expect(createModelRoute.method).toBe('POST'); + expect(createModelRoute.path).toBe('/models'); + }); + + test('handler returns 401 when Authorization is missing', async () => { + const req = new Request('http://localhost/models', { method: 'POST' }); + const res = await createModelRoute.handler(req, {}); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(401); + const body = await (res as Response).json(); + expect(body.error).toBe('unauthorized'); + }); + + test('handler returns 201 when a bearer token is present', async () => { + const req = new Request('http://localhost/models', { + method: 'POST', + headers: { Authorization: 'Bearer test-token' }, + }); + const res = await createModelRoute.handler(req, {}); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(201); + const body = await (res as Response).json(); + expect(typeof body.id).toBe('string'); + expect(typeof body.title).toBe('string'); + expect(typeof body.geometryUrl).toBe('string'); + expect(typeof body.createdAt).toBe('string'); + }); +}); + +// --------------------------------------------------------------------------- +// Record View — POST /models/:id/views +// --------------------------------------------------------------------------- + +describe('@farish/api — record-view route', () => { + test('is POST /models/:id/views', () => { + expect(recordViewRoute.method).toBe('POST'); + expect(recordViewRoute.path).toBe('/models/:id/views'); + }); + + test('handler returns 204 No Content for a valid model id', async () => { + const req = new Request('http://localhost/models/some-id/views', { method: 'POST' }); + const res = await recordViewRoute.handler(req, { id: 'some-id' }); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(204); + }); + + test('handler returns 404 when id param is missing', async () => { + const req = new Request('http://localhost/models//views', { method: 'POST' }); + const res = await recordViewRoute.handler(req, { id: '' }); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(404); + }); +}); + +// --------------------------------------------------------------------------- +// Submit Rating — PUT /models/:id/rating +// --------------------------------------------------------------------------- + +describe('@farish/api — submit-rating route', () => { + test('is PUT /models/:id/rating', () => { + expect(submitRatingRoute.method).toBe('PUT'); + expect(submitRatingRoute.path).toBe('/models/:id/rating'); + }); + + test('payload returns spec-required aggregate fields', () => { + const payload = submitRatingPayload('model-abc', 4); + expect(typeof payload.ratingAvg).toBe('number'); + expect(typeof payload.ratingCount).toBe('number'); + expect(payload.ratingCount).toBeGreaterThan(0); + expect(payload.userStars).toBe(4); + }); + + test('handler returns 401 when Authorization is missing', async () => { + const req = new Request('http://localhost/models/abc/rating', { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ stars: 4 }), + }); + const res = await submitRatingRoute.handler(req, { id: 'abc' }); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(401); + }); + + test('handler returns 400 for out-of-range stars', async () => { + const req = new Request('http://localhost/models/abc/rating', { + method: 'PUT', + headers: { + Authorization: 'Bearer tok', + 'content-type': 'application/json', + }, + body: JSON.stringify({ stars: 7 }), + }); + const res = await submitRatingRoute.handler(req, { id: 'abc' }); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(400); + }); + + test('handler returns 200 with aggregate counters for valid request', async () => { + const req = new Request('http://localhost/models/abc/rating', { + method: 'PUT', + headers: { + Authorization: 'Bearer tok', + 'content-type': 'application/json', + }, + body: JSON.stringify({ stars: 5 }), + }); + const res = await submitRatingRoute.handler(req, { id: 'abc' }); + // Should return a plain object (JSON-serializable), not a Response + if (res instanceof Response) { + // If it IS a Response it must be 200 + expect(res.status).toBe(200); + } else { + const body = res as { ratingAvg: number; ratingCount: number; userStars: number }; + expect(typeof body.ratingAvg).toBe('number'); + expect(body.userStars).toBe(5); + } + }); +}); + +// --------------------------------------------------------------------------- +// Get Leaderboard — GET /leaderboard +// --------------------------------------------------------------------------- + +describe('@farish/api — get-leaderboard route', () => { + test('is GET /leaderboard', () => { + expect(getLeaderboardRoute.method).toBe('GET'); + expect(getLeaderboardRoute.path).toBe('/leaderboard'); + }); + + test('payload echoes board and period, returns ordered items', () => { + const payload = getLeaderboardPayload('rated', '1m', 10); + expect(payload.board).toBe('rated'); + expect(payload.period).toBe('1m'); + expect(Array.isArray(payload.items)).toBe(true); + expect(payload.items.length).toBeLessThanOrEqual(10); + }); + + test('every item has spec-required fields', () => { + const payload = getLeaderboardPayload('viewed', 'all', 5); + for (const item of payload.items) { + expect(typeof item.rank).toBe('number'); + expect(item.rank).toBeGreaterThan(0); + expect(typeof item.id).toBe('string'); + expect(typeof item.title).toBe('string'); + expect(typeof item.authorName).toBe('string'); + expect(typeof item.authorId).toBe('string'); + expect(typeof item.authorUsername).toBe('string'); + expect(typeof item.metricValue).toBe('number'); + expect(typeof item.createdAt).toBe('string'); + } + }); + + test('items are rank-ordered starting at 1', () => { + const payload = getLeaderboardPayload('popular', '1w', 5); + payload.items.forEach((item, i) => { + expect(item.rank).toBe(i + 1); + }); + }); + + test('handler returns 400 for invalid board', async () => { + const req = new Request('http://localhost/leaderboard?board=bad&period=1w'); + const res = await getLeaderboardRoute.handler(req, {}); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(400); + }); + + test('handler returns 400 for invalid period', async () => { + const req = new Request('http://localhost/leaderboard?board=rated&period=bad'); + const res = await getLeaderboardRoute.handler(req, {}); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(400); + }); + + test('handler returns 400 when board param is missing', async () => { + const req = new Request('http://localhost/leaderboard?period=1w'); + const res = await getLeaderboardRoute.handler(req, {}); + expect(res instanceof Response).toBe(true); + expect((res as Response).status).toBe(400); + }); +}); + +// --------------------------------------------------------------------------- +// Get User Profile — GET /users/:username +// --------------------------------------------------------------------------- + +describe('@farish/api — get-user-profile route', () => { + test('is GET /users/:username', () => { + expect(getUserProfileRoute.method).toBe('GET'); + expect(getUserProfileRoute.path).toBe('/users/:username'); + }); + + test('payload returns all spec-required fields', () => { + const payload = getUserProfilePayload('testuser'); + expect(payload.username).toBe('testuser'); + expect(typeof payload.displayName).toBe('string'); + expect(typeof payload.joinDate).toBe('string'); + expect(typeof payload.modelCount).toBe('number'); + expect(payload.modelCount).toBeGreaterThan(0); + expect(typeof payload.totalViews).toBe('number'); + // avatarUrl and avgRating may be null + expect(payload.avatarUrl === null || typeof payload.avatarUrl === 'string').toBe(true); + expect(payload.avgRating === null || typeof payload.avgRating === 'number').toBe(true); + }); + + test('same username always returns the same data (deterministic)', () => { + const a = getUserProfilePayload('alice'); + const b = getUserProfilePayload('alice'); + expect(a.displayName).toBe(b.displayName); + expect(a.modelCount).toBe(b.modelCount); + }); +}); + +// --------------------------------------------------------------------------- +// List User Models — GET /users/:username/models +// --------------------------------------------------------------------------- + +describe('@farish/api — list-user-models route', () => { + test('is GET /users/:username/models', () => { + expect(listUserModelsRoute.method).toBe('GET'); + expect(listUserModelsRoute.path).toBe('/users/:username/models'); + }); + + test('payload returns items array with pagination fields', () => { + const payload = listUserModelsPayload('alice', 1, 24); + expect(Array.isArray(payload.items)).toBe(true); + expect(payload.items.length).toBeGreaterThan(0); + expect(typeof payload.total).toBe('number'); + expect(typeof payload.page).toBe('number'); + expect(typeof payload.totalPages).toBe('number'); + }); + + test('every item has spec-required fields', () => { + const payload = listUserModelsPayload('bob', 1, 10); + for (const item of payload.items) { + expect(typeof item.id).toBe('string'); + expect(typeof item.title).toBe('string'); + expect(typeof item.viewCount).toBe('number'); + expect(typeof item.createdAt).toBe('string'); + } + }); + + test('page number is reflected in the response', () => { + const payload = listUserModelsPayload('carol', 3, 5); + expect(payload.page).toBe(3); + }); +}); diff --git a/services/api/src/routes/submit-rating.ts b/services/api/src/routes/submit-rating.ts new file mode 100644 index 0000000..6c6b560 --- /dev/null +++ b/services/api/src/routes/submit-rating.ts @@ -0,0 +1,72 @@ +/** + * @farish/api — `PUT /models/:id/rating` endpoint stub. + * + * Creates or replaces the authenticated user's star rating for a shared + * model. PUT semantics ensure repeated calls update rather than duplicate. + * + * This is a STUB: it validates auth and the `stars` field, then returns + * deterministic mock aggregate counters. Real storage is implemented in + * step 35+. + * Spec: `docs/api/submit-rating/SPEC.md`. + */ +import { type SubmitRatingResponse, ROUTES } from '@farish/api-contract'; +import { errorResponse, extractBearerToken } from '../router.ts'; +import type { Route } from '../router.ts'; + +/** + * Build a stub {@link SubmitRatingResponse} payload. + * + * The averages and counts are derived from `stars` to produce plausible but + * deterministic values without real storage. + */ +export function submitRatingPayload(id: string, stars: number): SubmitRatingResponse { + // Derive a stable seed from the model ID string for determinism. + const seed = + id.split('').reduce((acc, ch) => ((acc << 5) - acc + ch.charCodeAt(0)) | 0, 0) >>> 0; + const existingCount = 10 + (seed % 90); + const existingSum = 3.5 * existingCount; + const newSum = existingSum + stars; + const newCount = existingCount + 1; + const ratingAvg = Math.round((newSum / newCount) * 100) / 100; + return { ratingAvg, ratingCount: newCount, userStars: stars }; +} + +/** The mountable submit-rating route. */ +export const submitRatingRoute: Route = { + method: 'PUT', + path: ROUTES.submitRating, + handler: async (req, params) => { + // Auth is required — reject unauthenticated requests with 401. + const token = extractBearerToken(req); + if (token === null) { + return errorResponse('unauthorized', 401); + } + + const { id } = params; + if (!id) return errorResponse('model_not_found', 404); + + // Parse the JSON body and validate `stars`. + let body: unknown; + try { + body = await req.json(); + } catch { + return errorResponse('invalid_stars', 400); + } + + if ( + typeof body !== 'object' || + body === null || + !('stars' in body) || + typeof (body as Record).stars !== 'number' + ) { + return errorResponse('invalid_stars', 400); + } + + const stars = (body as Record).stars as number; + if (!Number.isInteger(stars) || stars < 1 || stars > 5) { + return errorResponse('invalid_stars', 400); + } + + return submitRatingPayload(id, stars); + }, +}; diff --git a/services/api/src/server.integration.test.ts b/services/api/src/server.integration.test.ts new file mode 100644 index 0000000..3272f09 --- /dev/null +++ b/services/api/src/server.integration.test.ts @@ -0,0 +1,252 @@ +/** + * @farish/api — server integration test. + * + * Boots a real `Bun.serve` instance on an ephemeral port (port 0) and exercises + * the API over actual HTTP. This proves the framework wires together + * end-to-end: router + routes + Bun.serve. + * + * Every one of the 8 spec endpoints is smoke-tested here (happy path + + * the most important error path). Detailed payload-shape testing lives + * in `routes.test.ts`. + */ +import { afterAll, beforeAll, describe, expect, test } from 'bun:test'; +import type { Server } from 'bun'; +import { createRouter } from './router.ts'; +import { routes } from './routes/index.ts'; + +let server: Server; +let baseUrl: string; + +beforeAll(() => { + // Port 0 → the OS assigns a free port, so tests never collide. + server = Bun.serve({ port: 0, fetch: createRouter(routes) }); + baseUrl = `http://localhost:${String(server.port)}`; +}); + +afterAll(() => { + server.stop(true); +}); + +// --------------------------------------------------------------------------- +// Infrastructure +// --------------------------------------------------------------------------- + +describe('@farish/api — server over HTTP', () => { + test('GET /health responds 200 ok', async () => { + const res = await fetch(`${baseUrl}/health`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.status).toBe('ok'); + expect(body.service).toBe('api'); + }); + + test('an unknown path responds 404 not_found', async () => { + const res = await fetch(`${baseUrl}/does-not-exist`); + expect(res.status).toBe(404); + expect(await res.json()).toEqual({ error: 'not_found' }); + }); + + test('CORS preflight OPTIONS returns 204 with origin header', async () => { + const res = await fetch(`${baseUrl}/models`, { method: 'OPTIONS' }); + expect(res.status).toBe(204); + expect(res.headers.get('access-control-allow-origin')).toBe('*'); + }); +}); + +// --------------------------------------------------------------------------- +// List Models — GET /models +// --------------------------------------------------------------------------- + +describe('@farish/api — GET /models', () => { + test('responds 200 with items array and pagination', async () => { + const res = await fetch(`${baseUrl}/models`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(Array.isArray(body.items)).toBe(true); + expect(typeof body.total).toBe('number'); + expect(typeof body.page).toBe('number'); + expect(typeof body.totalPages).toBe('number'); + }); + + test('invalid sort returns 400 invalid_sort', async () => { + const res = await fetch(`${baseUrl}/models?sort=nope`); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('invalid_sort'); + }); + + test('limit exceeding max returns 400 limit_too_large', async () => { + const res = await fetch(`${baseUrl}/models?limit=999`); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('limit_too_large'); + }); +}); + +// --------------------------------------------------------------------------- +// Get Model — GET /models/:id +// --------------------------------------------------------------------------- + +describe('@farish/api — GET /models/:id', () => { + test('responds 200 with a full model record', async () => { + const res = await fetch(`${baseUrl}/models/some-test-id`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(typeof body.id).toBe('string'); + expect(typeof body.prompt).toBe('string'); + expect(typeof body.geometryUrl).toBe('string'); + expect(typeof body.viewCount).toBe('number'); + expect(typeof body.ratingCount).toBe('number'); + }); +}); + +// --------------------------------------------------------------------------- +// Create Model — POST /models +// --------------------------------------------------------------------------- + +describe('@farish/api — POST /models', () => { + test('returns 401 when no auth header', async () => { + const res = await fetch(`${baseUrl}/models`, { method: 'POST' }); + expect(res.status).toBe(401); + expect((await res.json()).error).toBe('unauthorized'); + }); + + test('returns 201 with a model record when auth is present', async () => { + const res = await fetch(`${baseUrl}/models`, { + method: 'POST', + headers: { Authorization: 'Bearer test-token' }, + }); + expect(res.status).toBe(201); + const body = await res.json(); + expect(typeof body.id).toBe('string'); + expect(typeof body.geometryUrl).toBe('string'); + }); +}); + +// --------------------------------------------------------------------------- +// Record View — POST /models/:id/views +// --------------------------------------------------------------------------- + +describe('@farish/api — POST /models/:id/views', () => { + test('returns 204 No Content', async () => { + const res = await fetch(`${baseUrl}/models/view-test-id/views`, { method: 'POST' }); + expect(res.status).toBe(204); + expect(res.headers.get('access-control-allow-origin')).toBe('*'); + }); +}); + +// --------------------------------------------------------------------------- +// Submit Rating — PUT /models/:id/rating +// --------------------------------------------------------------------------- + +describe('@farish/api — PUT /models/:id/rating', () => { + test('returns 401 when no auth header', async () => { + const res = await fetch(`${baseUrl}/models/abc/rating`, { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ stars: 4 }), + }); + expect(res.status).toBe(401); + expect((await res.json()).error).toBe('unauthorized'); + }); + + test('returns 400 for out-of-range stars', async () => { + const res = await fetch(`${baseUrl}/models/abc/rating`, { + method: 'PUT', + headers: { Authorization: 'Bearer tok', 'content-type': 'application/json' }, + body: JSON.stringify({ stars: 0 }), + }); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('invalid_stars'); + }); + + test('returns 200 with aggregate counters for valid request', async () => { + const res = await fetch(`${baseUrl}/models/abc/rating`, { + method: 'PUT', + headers: { Authorization: 'Bearer tok', 'content-type': 'application/json' }, + body: JSON.stringify({ stars: 4 }), + }); + expect(res.status).toBe(200); + const body = await res.json(); + expect(typeof body.ratingAvg).toBe('number'); + expect(typeof body.ratingCount).toBe('number'); + expect(body.userStars).toBe(4); + }); +}); + +// --------------------------------------------------------------------------- +// Get Leaderboard — GET /leaderboard +// --------------------------------------------------------------------------- + +describe('@farish/api — GET /leaderboard', () => { + test('responds 200 with board, period, and items', async () => { + const res = await fetch(`${baseUrl}/leaderboard?board=rated&period=1m`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.board).toBe('rated'); + expect(body.period).toBe('1m'); + expect(Array.isArray(body.items)).toBe(true); + expect(body.items.length).toBeGreaterThan(0); + }); + + test('returns 400 for invalid board', async () => { + const res = await fetch(`${baseUrl}/leaderboard?board=bad&period=1w`); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('invalid_board'); + }); + + test('returns 400 for missing period', async () => { + const res = await fetch(`${baseUrl}/leaderboard?board=viewed`); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('invalid_period'); + }); +}); + +// --------------------------------------------------------------------------- +// Get User Profile — GET /users/:username +// --------------------------------------------------------------------------- + +describe('@farish/api — GET /users/:username', () => { + test('responds 200 with profile fields', async () => { + const res = await fetch(`${baseUrl}/users/alice`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.username).toBe('alice'); + expect(typeof body.displayName).toBe('string'); + expect(typeof body.modelCount).toBe('number'); + expect(typeof body.totalViews).toBe('number'); + }); +}); + +// --------------------------------------------------------------------------- +// List User Models — GET /users/:username/models +// --------------------------------------------------------------------------- + +describe('@farish/api — GET /users/:username/models', () => { + test('responds 200 with items array and pagination', async () => { + const res = await fetch(`${baseUrl}/users/alice/models`); + expect(res.status).toBe(200); + const body = await res.json(); + expect(Array.isArray(body.items)).toBe(true); + expect(typeof body.total).toBe('number'); + expect(typeof body.page).toBe('number'); + expect(typeof body.totalPages).toBe('number'); + }); + + test('limit exceeding max returns 400', async () => { + const res = await fetch(`${baseUrl}/users/bob/models?limit=999`); + expect(res.status).toBe(400); + expect((await res.json()).error).toBe('limit_too_large'); + }); + + test('/users/:username/models is matched before /users/:username', async () => { + // This verifies route ordering in index.ts — without the fix, the more + // specific sub-path would be swallowed by the profile route. + const profileRes = await fetch(`${baseUrl}/users/carol`); + const modelsRes = await fetch(`${baseUrl}/users/carol/models`); + expect(profileRes.status).toBe(200); + expect(modelsRes.status).toBe(200); + const profile = await profileRes.json(); + const models = await modelsRes.json(); + expect(profile.username).toBe('carol'); + expect(Array.isArray(models.items)).toBe(true); + }); +}); diff --git a/services/api/src/server.ts b/services/api/src/server.ts new file mode 100644 index 0000000..a7c5831 --- /dev/null +++ b/services/api/src/server.ts @@ -0,0 +1,28 @@ +/** + * @farish/api — server entrypoint. + * + * Bootstraps the development API server: composes every registered route into + * one `fetch` handler and binds it with `Bun.serve`. Started by `bun run start` + * and orchestrated by the repo `Tiltfile` for local development. + * + * The port is read from `PORT` (default 8787) so Tiltfile and CI can pick a + * free port. Kept thin: all behaviour lives in `router.ts` and `routes/`. + */ +import { createRouter } from './router.ts'; +import { routes } from './routes/index.ts'; + +/** Port the dev server binds. Override with the `PORT` env var. */ +const PORT = Number(process.env.PORT ?? '8787'); + +const fetch = createRouter(routes); + +const server = Bun.serve({ port: PORT, fetch }); + +// Services obey biome's noConsole rule; the error channel is permitted and is +// the right place for a one-time startup notice. +console.error( + `[api] farish API server listening on http://localhost:${String(server.port)}`, +); +console.error( + `[api] routes: ${routes.map((r) => `${r.method} ${r.path}`).join(', ')}`, +); diff --git a/services/api/tsconfig.json b/services/api/tsconfig.json new file mode 100644 index 0000000..e8f2048 --- /dev/null +++ b/services/api/tsconfig.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "src/**/*.test.ts"], + "references": [ + { "path": "../../lib/api-contract" }, + { "path": "../../lib/mock-data" } + ] +} diff --git a/services/echo-service/package.json b/services/echo-service/package.json new file mode 100644 index 0000000..1df16a1 --- /dev/null +++ b/services/echo-service/package.json @@ -0,0 +1,17 @@ +{ + "name": "@farish/echo-service", + "version": "0.0.0", + "private": true, + "description": "Dummy service — a long-running HTTP server. Exercises the nx graph as a service node.", + "type": "module", + "scripts": { + "lint": "biome lint ./src", + "format": "biome check --write ./src", + "test": "bun test", + "build": "tsc -p tsconfig.json", + "start": "bun run src/server.ts" + }, + "dependencies": { + "@farish/core": "workspace:*" + } +} diff --git a/services/echo-service/project.json b/services/echo-service/project.json new file mode 100644 index 0000000..4f033b2 --- /dev/null +++ b/services/echo-service/project.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "echo-service", + "projectType": "application", + "sourceRoot": "services/echo-service/src", + "tags": ["scope:service"], + "targets": { + "lint": { "executor": "nx:run-script", "options": { "script": "lint" } }, + "format": { "executor": "nx:run-script", "options": { "script": "format" } }, + "test": { "executor": "nx:run-script", "options": { "script": "test" } }, + "build": { "executor": "nx:run-script", "options": { "script": "build" } } + } +} diff --git a/services/echo-service/src/handler.test.ts b/services/echo-service/src/handler.test.ts new file mode 100644 index 0000000..69cf90e --- /dev/null +++ b/services/echo-service/src/handler.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, test } from 'bun:test'; +import { handleEcho } from './handler.ts'; + +describe('@farish/echo-service', () => { + test('handleEcho echoes a greeting for the given name', async () => { + const res = handleEcho('tester'); + expect(await res.text()).toBe('Hello, tester, from farish core.'); + }); +}); diff --git a/services/echo-service/src/handler.ts b/services/echo-service/src/handler.ts new file mode 100644 index 0000000..993f431 --- /dev/null +++ b/services/echo-service/src/handler.ts @@ -0,0 +1,14 @@ +/** + * @farish/echo-service — request handler. + * + * Split from the server entrypoint so it is unit-testable without binding a + * port. Depends on `@farish/core`. + */ +import { greet } from '@farish/core'; + +/** Build the HTTP response for an echo request. */ +export function handleEcho(name: string): Response { + return new Response(greet(name), { + headers: { 'content-type': 'text/plain' }, + }); +} diff --git a/services/echo-service/src/server.ts b/services/echo-service/src/server.ts new file mode 100644 index 0000000..4ee3116 --- /dev/null +++ b/services/echo-service/src/server.ts @@ -0,0 +1,21 @@ +/** + * @farish/echo-service — server entrypoint. + * + * A long-running service (contrast with `apps/cli`, which exits). Started with + * `bun run start`. Kept thin: all logic lives in `handler.ts`. + */ +import { handleEcho } from './handler.ts'; + +const PORT = 3001; + +const server = Bun.serve({ + port: PORT, + fetch(req): Response { + const name = new URL(req.url).searchParams.get('name') ?? 'anonymous'; + return handleEcho(name); + }, +}); + +// Allowed in services? No — services obey noConsole. Use the error channel, +// which biome.json permits, for a one-time startup notice. +console.error(`echo-service listening on http://localhost:${String(server.port)}`); diff --git a/services/echo-service/tsconfig.json b/services/echo-service/tsconfig.json new file mode 100644 index 0000000..7118af5 --- /dev/null +++ b/services/echo-service/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "src/**/*.test.ts"], + "references": [{ "path": "../../lib/core" }] +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..593c108 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "//": "Shared strict TypeScript base. Every package's tsconfig.json extends this. Docs: docs/monorepo/bun.md", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["bun"], + + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "resolveJsonModule": true, + + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "exclude": ["node_modules", "dist"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ae8eb4d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "//": "Root solution tsconfig — references every tsc-built package so editors and `tsc --build` see the whole graph. `apps/web` is intentionally absent: it is a Vue app built by Vite (vite build / vue-tsc), not part of the tsc solution. Docs: docs/monorepo/bun.md", + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "files": [], + "references": [ + { "path": "./lib/core" }, + { "path": "./lib/math" }, + { "path": "./lib/api-contract" }, + { "path": "./lib/mock-data" }, + { "path": "./packages/sdk" }, + { "path": "./apps/cli" }, + { "path": "./services/api" }, + { "path": "./services/echo-service" } + ] +}