From 49c1cff8a527fcaec01e66767bd13428bd3ea39f Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 11:44:08 -0800 Subject: [PATCH 001/291] Add framework evolution plan and internal architecture docs --- README-framework-plan.md | 319 +++++++++++++++++++++++++++++++++ docs/README.md | 38 ++++ docs/ai-agent-guide.md | 89 +++++++++ docs/architecture.md | 115 ++++++++++++ docs/data-model.md | 86 +++++++++ docs/development-workflows.md | 80 +++++++++ docs/http-routes.md | 116 ++++++++++++ docs/known-gaps-and-risks.md | 80 +++++++++ docs/project-scope-analysis.md | 140 +++++++++++++++ 9 files changed, 1063 insertions(+) create mode 100644 README-framework-plan.md create mode 100644 docs/README.md create mode 100644 docs/ai-agent-guide.md create mode 100644 docs/architecture.md create mode 100644 docs/data-model.md create mode 100644 docs/development-workflows.md create mode 100644 docs/http-routes.md create mode 100644 docs/known-gaps-and-risks.md create mode 100644 docs/project-scope-analysis.md diff --git a/README-framework-plan.md b/README-framework-plan.md new file mode 100644 index 00000000..1b930118 --- /dev/null +++ b/README-framework-plan.md @@ -0,0 +1,319 @@ +# GoShip Framework Plan + +This document tracks the plan to evolve GoShip from a starter kit into a Rails-like Go framework with strong developer ergonomics. + +## Terminology Note + +Voice-to-text aliases used in discussion: + +- `shiri` => `Cherie` +- `jerry` => `Cherie` +- `sherry` => `Cherie` + +When planning or tracking work, treat all of the above as `Cherie`. + +## Vision + +Build a highly productive, convention-first Go framework where developers can: + +1. ship fast with sensible defaults; +2. install batteries as versioned modules; +3. choose deployment/runtime mode without rewriting app code. + +## Core Product Goals + +1. Rails-like productivity in Go. +2. LLM-first developer experience. +3. Convention over configuration. +4. Modular infrastructure adapters. +5. Strong defaults with optional escape hatches. + +## Confirmed Decisions + +1. Keep Ent as the ORM for now. +2. Use a monorepo with multiple Go modules plus `go.work` for maintainers. +3. Ship installable/versioned modules (auth, billing, notifications, storage, admin). +4. Keep one blessed default stack, but support both single-node and distributed runtime modes via adapters. + +## Upstream/Downstream Relationship + +GoShip is the framework upstream. +Cherie is a downstream product built on top of GoShip. + +Framework work must include a sync path so Cherie stays current without fragile manual cherry-picking. + +## Candidate Capabilities To Pull From Cherie + +Based on Cherie docs and current implementation, these are strong candidates to upstream into GoShip modules: + +1. Realtime baseline that is fully wired (SSE endpoint + unread counts + notification center patterns). +2. Mature notification permissions model (type + platform + grant/revoke lifecycle). +3. Background job patterns for daily/periodic workflows (retention, maintenance, notification orchestration). +4. Referral system primitives (link generation, attribution, reward application). +5. Gamification primitives (points/progression hooks) as optional module. +6. Multi-app branding strategy from one codebase (app profile/brand config). +7. Security hardening patterns: +- route-level authorization checks for resource interaction; +- friend/relationship ownership checks where relevant; +- explicit forbidden/not-found behavior for unauthorized access. +8. Production operations runbooks: +- deploy profile separation; +- cache invalidation/ops guidance; +- migration caveats and guardrails. + +## Cherie Sync Policy + +1. Every GoShip framework milestone must include a Cherie compatibility check. +2. Breaking changes require: +- migration notes; +- codemods or scripted upgrade steps where possible; +- a compatibility window policy. +3. Maintain a living "GoShip -> Cherie adoption board" with statuses: +- `not started` +- `in progress` +- `adopted` +- `blocked` +4. Do not merge major framework refactors without validating Cherie boot, auth flow, and realtime flow. + +## Runtime Modes + +### 1) Single-Node Mode + +- DB: SQLite +- Cache: in-memory +- PubSub: in-process +- Jobs: in-process scheduler/worker + +### 2) Distributed Mode + +- DB: Postgres +- Cache: Redis +- PubSub: Redis-backed +- Jobs: Asynq + +## Required Core Interfaces + +Create stable contracts in `core` so app code is backend-agnostic: + +1. `Store` (database/repository boundary) +2. `Cache` +3. `PubSub` +4. `Jobs` +5. `SessionStore` +6. `BlobStore` +7. `Auth` +8. `Billing` +9. `Notifications` + +## Rails-Like Capabilities to Implement First + +1. `goship new ` CLI with a 2-minute happy path. +2. Generators: +- `goship g model` +- `goship g scaffold` +- `goship g migration` +- `goship g job` +- `goship g mailer` +- `goship g module install ` +3. ActiveStorage-like file attachments: +- attach files to entities; +- support local + S3 backends; +- simple URL + variant APIs. +4. Admin scaffolding from Ent schema. +5. Background jobs with retries/scheduling. + +## Frontend Strategy (HTMX-First, Svelte as Islands) + +Svelte should remain optional and isolated. Current pattern in Cherie/GoShip bundles all Svelte entrypoints into one global `svelte_bundle.js`, which increases payload and causes coupling. + +Target approach: + +1. Keep HTMX + Templ as default. +2. Load Svelte only for pages/components that need it. +3. Replace single global Svelte bundle with per-island chunks (no single global Svelte build artifact loaded site-wide). +4. Use auto-discovery to find islands and generate a manifest + runtime registry automatically. +5. Use dynamic imports so `renderSvelteComponent(name, ...)` loads code on demand. +6. Avoid globally injecting `svelte_bundle.js` for all pages. + +Important DX constraint: + +- No manual island wiring by default. +- Island wiring should be generated by CLI/scaffolding. + +### Auto-Discovery + Auto-Wire Model + +Desired developer flow: + +1. Create a Svelte component either: +- next to its usage (colocated), or +- in a central reusable island library. +2. Reference it in Templ with a helper/component tag and props. +3. Build step auto-discovers island files and generates: +- island manifest (`component -> js/css asset`); +- runtime registry for lazy mounting; +- optional typed helper stubs. +4. Templ helper injects only needed island script(s) for that page/partial. + +No manual edits should be required for: + +- central JS registry files; +- script tag wiring in templates; +- per-component import maps. + +## Svelte/Islands TODOs + +- [ ] Replace single `svelte_bundle.js` build with multiple outputs (code-split entries). +- [ ] Add an island manifest mapping `componentName -> asset path`. +- [ ] Add template helper to include only required island scripts per page. +- [ ] Support HTMX swap lifecycle: mount/unmount Svelte instances safely after partial swaps. +- [ ] Benchmark before/after page payload and interaction latency. +- [ ] Document "when to use Svelte vs Alpine vs vanilla vs pure HTMX" as framework guidance. +- [ ] Add `goship g island ` to generate component + entrypoint + registration with zero manual edits. +- [ ] Support colocated islands and central-library islands with the same discovery pipeline. +- [ ] Add a watch mode that re-generates island manifest/registry automatically during development. + +## Frontend Alternatives (No-Compile Paths) + +Question: can we load raw `.svelte` components directly in browser without build? + +Answer: + +- Not as a production default. Svelte is compile-based. + +Viable alternatives for no-build interactivity: + +1. HTMX + Alpine (preferred default for small/medium interactions). +2. Vanilla Web Components for reusable widgets. +3. Lightweight runtime libraries (e.g. Petite-Vue) where appropriate. + +Decision: + +- Keep Svelte optional for advanced islands. +- Keep HTMX-first and no-build-friendly by default. +- Ensure CLI removes manual compile-pipeline pain where Svelte is used. + +## Testing Strategy (Developer Ergonomics First) + +Test workflow should be fast and local-first without requiring Docker for most feedback loops. + +Principles: + +1. Maximize unit tests and table-driven tests for business logic. +2. Push side effects behind interfaces to allow in-memory fakes. +3. Keep integration tests focused and limited (happy-path + critical failure cases). +4. Keep end-to-end tests minimal and scenario-driven. +5. Avoid making Docker a prerequisite for routine test runs. + +Target pyramid: + +1. Unit tests (majority): pure Go, table tests, no network, no containers. +2. Integration tests (few): DB/repo boundaries and adapter contracts. +3. E2E tests (very few): key user journeys only. + +## Module Plan + +Proposed module boundaries: + +1. `packages/core` +2. `packages/auth` +3. `packages/billing` +4. `packages/notifications` +5. `packages/storage` +6. `packages/admin` +7. `cli/goship` + +## Priority Roadmap + +### Phase 0: Stabilize Current Base + +1. Fix container initialization mismatch and shutdown safety. +2. Resolve realtime/notification route wiring drift. +3. Clean and align current docs with actual runtime behavior. +4. Refresh stale e2e coverage for critical flows. + +### Phase 1: Core Abstractions + +1. Define `core` interfaces for DB/cache/pubsub/jobs/storage. +2. Implement adapters: +- SQLite + Postgres +- memory-cache + Redis-cache +- inproc-pubsub + Redis-pubsub +- inproc-jobs + Asynq +3. Add runtime mode config (`single-node`, `distributed`). + +### Phase 2: Monorepo and Module Packaging + +1. Restructure into multi-module layout. +2. Add `go.work` for local development across modules. +3. Establish semver tagging and module release process. +4. Define how Cherie consumes modules during local dev (`go.work`) vs released versions (tags). + +### Phase 3: CLI and Generators + +1. Build `goship` CLI. +2. Implement app bootstrap and generator commands. +3. Add idempotent install/wire commands for optional modules. + +### Phase 4: Batteries and DX + +1. Deliver auth, storage, notifications, billing, admin modules. +2. Implement ActiveStorage-like attachment primitives. +3. Improve diagnostics, error pages, and test templates. + +### Phase 5: LLM-First Tooling + +1. Add `llm.txt` as machine-readable framework reference. +2. Add an MCP server exposing commands, module contracts, and examples. +3. Generate concise human docs from the same source of truth. + +## TODO Checklist + +## Immediate + +- [ ] Decide and document exact package naming convention (`goship/*`). +- [ ] Choose CLI implementation approach (`cobra`, `urfave/cli`, or stdlib). +- [ ] Draft `core` interface contracts in a design doc. +- [ ] Define runtime config schema for adapter selection. +- [ ] Specify module compatibility/version policy. +- [ ] Create a developer-facing README + LLM-facing README/`llm.txt` split with one source of truth. +- [ ] Define MCP server scope for GoShip (commands, module APIs, recipes, migration help). +- [ ] Create a `CHERIE_SYNC.md` runbook (upgrade process + rollback + validation checklist). +- [ ] Create a baseline compatibility test suite for Cherie critical paths. +- [ ] Define testing standards doc: what must be unit-testable and where table tests are required. + +## Near-Term + +- [ ] Build first adapter pair: `sqlite` + `postgres`. +- [ ] Build first cache pair: `memory` + `redis`. +- [ ] Build first pubsub pair: `inproc` + `redis`. +- [ ] Build first jobs pair: `inproc` + `asynq`. +- [ ] Prototype attachment API with local and S3 storage. +- [ ] Refactor high-logic route/service code into testable units with interface boundaries. +- [ ] Add in-memory test doubles for cache/pubsub/jobs/storage adapters. +- [ ] Ensure default `make test` runs without Docker. + +## Mid-Term + +- [ ] Release `goship new` CLI command. +- [ ] Release `goship g model` and `goship g migration`. +- [ ] Release `auth` and `storage` modules. +- [ ] Release `admin` scaffolding MVP. +- [ ] Add golden-path example apps for both runtime modes. +- [ ] Move Cherie onto released GoShip modules incrementally (module by module). +- [ ] Upstream selected Cherie capabilities into optional GoShip modules (notifications/referrals/gamification/security helpers). +- [ ] Keep Docker-based integration suite as optional/CI-focused (`make test-integration`), not default local path. + +## Open Questions + +1. How strict should conventions be before allowing customization hooks? +2. Which features are mandatory in v1 versus module-only? +3. What is the minimum stable API surface for `core` v1.0.0? +4. How should we guarantee cross-module compatibility at release time? + +## Definition of Success (v1) + +1. A developer can run `goship new myapp` and ship a working app quickly. +2. The same app code can run in single-node or distributed mode by config. +3. Optional batteries are added via CLI without copy-paste. +4. LLMs can reliably reason over framework structure using `llm.txt` + MCP. +5. Cherie can upgrade to current GoShip with a documented, repeatable process. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..04d5fd4a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,38 @@ +# Internal Documentation (Developers and AI Agents) + +This `docs/` directory is internal and implementation-focused. +It is not intended as user-facing product documentation. + +## Goals + +- Explain what the project does today based on code, not assumptions. +- Give developers and AI agents a fast map of where to make changes. +- Capture system risks and incomplete areas so work is directed intentionally. + +## Document Index + +- `project-scope-analysis.md`: End-to-end feature and capability analysis. +- `architecture.md`: Runtime architecture, request flow, and service composition. +- `http-routes.md`: Route inventory grouped by access level and purpose. +- `data-model.md`: Ent entities and domain model coverage. +- `ai-agent-guide.md`: Practical guide for AI agents working in this repo. +- `known-gaps-and-risks.md`: Confirmed implementation gaps and technical risks. +- `development-workflows.md`: Day-to-day run/build/test/migration workflows. + +## Primary Source Files Used For This Analysis + +- `cmd/web/main.go` +- `cmd/worker/main.go` +- `cmd/seed/main.go` +- `pkg/services/container.go` +- `pkg/routing/routes/router.go` +- `pkg/routing/routes/*.go` +- `pkg/tasks/*.go` +- `pkg/repos/**/*.go` +- `ent/schema/*.go` +- `config/config.go` +- `config/config.yaml` +- `Makefile` +- `build.mjs` +- `package.json` +- `e2e_tests/tests/goship.spec.ts` diff --git a/docs/ai-agent-guide.md b/docs/ai-agent-guide.md new file mode 100644 index 00000000..4c7e196a --- /dev/null +++ b/docs/ai-agent-guide.md @@ -0,0 +1,89 @@ +# AI Agent Guide + +This guide is for code agents making changes in this repository. + +## Start Here + +1. Read `docs/project-scope-analysis.md` and `docs/known-gaps-and-risks.md`. +2. Inspect route wiring in `pkg/routing/routes/router.go` before editing handlers. +3. Inspect `pkg/services/container.go` before assuming a dependency is initialized. + +## Architectural Conventions + +- HTTP handlers live in `pkg/routing/routes`. +- Domain logic should prefer repository packages (`pkg/repos/...`) over route-level DB logic. +- Rendering is typically done via `controller.Page` + templ components. +- Enums/constants are centralized in `pkg/domain`. + +## Safe Change Workflow + +1. Identify layer to change: +- routing +- repository/service +- domain/schema +- template/frontend + +2. Check for related tests: +- `rg "func Test" pkg/...` +- route tests in `pkg/routing/routes/*_test.go` + +3. Implement minimal, local change first. +4. Run targeted tests, then broader tests if needed. +5. Update docs in `docs/` when behavior or architecture changes. + +## Key Files By Concern + +Runtime bootstrap: + +- `cmd/web/main.go` +- `cmd/worker/main.go` +- `cmd/seed/main.go` + +Dependency wiring: + +- `pkg/services/container.go` +- `pkg/services/auth.go` +- `pkg/services/tasks.go` + +Routing and middleware: + +- `pkg/routing/routes/router.go` +- `pkg/middleware/*.go` + +Data and domain: + +- `ent/schema/*.go` +- `pkg/repos/**/*.go` +- `pkg/domain/*.go` + +UI and rendering: + +- `pkg/controller/*.go` +- `templates/**/*.templ` +- `javascript/**/*` + +## Common Pitfalls + +- Assuming cache/notifier/task clients are initialized in the container. +- Implementing a route but not registering it in `router.go`. +- Adding schema logic without checking migration/generation workflow. +- Updating frontend behavior without checking templ + JS integration points. + +## Commands Commonly Used + +- `make watch` (multi-process local dev) +- `make test` (Go tests) +- `make worker` (async worker) +- `make build-js` +- `make build-css` +- `make ent-gen` +- `make makemigrations name=YourChange` + +## Documentation Rule + +When code behavior changes, update at least: + +- `docs/project-scope-analysis.md` if capability changed +- `docs/http-routes.md` if route surface changed +- `docs/known-gaps-and-risks.md` if a risk was added/removed + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..c6466bb0 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,115 @@ +# Architecture + +## High-Level Layout + +The application follows a layered structure: + +- `cmd/*`: process entrypoints (`web`, `worker`, `seed`) +- `pkg/services`: dependency container and infrastructure clients +- `pkg/routing/routes`: HTTP handlers and route composition +- `pkg/middleware`: auth/session/cache/onboarding/request middleware +- `pkg/repos`: data access and external service adapters +- `pkg/controller`: rendering, page object, redirect helpers +- `templates`: Templ UI components/layouts/pages/emails +- `ent`: schema + generated ORM +- `pkg/tasks`: Asynq task processors + +## Web Runtime Flow + +1. `cmd/web/main.go` creates container via `services.NewContainer()`. +2. `routes.BuildRouter(c)` configures middleware stack and registers routes. +3. Echo server starts with request timeout middleware (SSE-aware). +4. Request path executes middleware chain, route handler, and page rendering. + +## Worker Runtime Flow + +1. `cmd/worker/main.go` loads config and starts Asynq server. +2. Creates app container and builds router (for reverse route URLs in tasks). +3. Constructs repo instances needed by task processors. +4. Registers handlers on Asynq mux and runs worker. + +## Container Composition + +`pkg/services/container.go` is the core composition root. + +Currently initialized in `NewContainer()`: + +- Config +- Validator +- Echo web server + logger +- DB connection +- Ent ORM +- Auth client +- Mail client +- Stripe API key setup + +Not currently initialized (commented out): + +- Cache client +- Notifier repo +- Task client + +This mismatch affects parts of the runtime that assume those dependencies exist. See `known-gaps-and-risks.md`. + +## HTTP Middleware Stack + +Primary middleware set in `pkg/routing/routes/router.go` includes: + +- Trailing slash normalization +- Panic recovery +- Security headers +- Request ID +- Gzip +- Structured request logging +- Request timeout (SSE skipped) +- Session middleware +- Authenticated user hydration +- CSRF middleware +- Device type tagging + +Additional gatekeepers: + +- `RequireAuthentication` +- `RequireNoAuthentication` +- `RedirectToOnboardingIfNotComplete` +- Password token validity loader + +## Rendering Model + +The UI is server-rendered using Templ components. + +- Base page abstraction: `pkg/controller/page.go` +- Render orchestration: `pkg/controller/controller.go` +- Layout wrappers: `templates/layouts/*.templ` +- Route page components: `templates/pages/*.templ` + +HTMX behavior is integrated in the page object (`Page.HTMX`) and controller render logic. + +## Data Layer + +- Ent ORM (`ent`) is authoritative for schema and query generation. +- Schema create/migrate is invoked in app startup via `c.ORM.Schema.Create(...)`. +- Repository packages encapsulate higher-level domain operations. + +## Async + Notifications Architecture + +- Asynq handles background jobs with Redis backend. +- Notification system is designed around: + - persistent DB notifications + - pub/sub events for SSE + - push channels (PWA + FCM) +- SSE endpoint exists (`pkg/routing/routes/realtime.go`) but route wiring is currently disabled. + +## Frontend Asset Architecture + +- `build.mjs` bundles Svelte entrypoints under `javascript/svelte/*.js` +- Also bundles vanilla JS from `javascript/vanilla/main.js` +- Outputs static bundles and meta files in `static/` +- Tailwind build pipeline outputs `static/styles_bundle.css` + +## Deployment/Operations Shape + +- Local process orchestration via Overmind (`Procfile`) +- Docker Compose for Redis + Mailpit in current config +- Kamal deployment files present (`deploy/`, `.kamal/`) + diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 00000000..5355860f --- /dev/null +++ b/docs/data-model.md @@ -0,0 +1,86 @@ +# Data Model + +Primary schema is defined in `ent/schema/*.go` and compiled into generated Ent code in `ent/`. + +## Core Entities + +Identity and profile: + +- `User` +- `Profile` +- `PasswordToken` +- `LastSeenOnline` + +Communication and notifications: + +- `Notification` +- `NotificationPermission` +- `NotificationTime` +- `PwaPushSubscription` +- `FCMSubscriptions` +- `SentEmail` + +Billing: + +- `MonthlySubscription` + +Media and file storage: + +- `Image` +- `ImageSize` +- `FileStorage` + +Other domain support: + +- `EmailSubscription` +- `EmailSubscriptionType` +- `Invitation` +- `PhoneVerificationCode` +- `Emojis` + +## Important Relationships + +- `User` has one `Profile` (`User.profile` unique edge). +- `Profile` has many `notifications`, `photos`, `invitations`, push subscriptions, and notification permissions. +- `MonthlySubscription` has one `payer` profile and many `benefactors` profiles. +- `Notification` belongs to one `Profile`. +- `NotificationPermission` is unique per `(profile_id, permission, platform)`. +- `NotificationTime` is unique per `(profile_id, type)`. +- `Image` has many `ImageSize` records. +- `ImageSize` has one required `FileStorage` object. + +## Domain Enums + +Defined in `pkg/domain/enum.go`: + +- Notification types +- Notification permission types +- Notification delivery platforms +- Image sizes and categories +- Product type (free/pro) +- Bottom navbar item +- Email subscription list + +## Subscription Model Notes + +Current app logic treats free plan as absence of an active pro subscription. + +- Creation path often creates trial pro subscription on onboarding. +- Active subscription uniqueness is enforced by unique index on `(paying_profile_id, is_active)`. + +## Notification Model Notes + +Notification records include: + +- Type/title/text/link +- Read and read timestamp +- Optional actor/resource linkage +- Per-profile ownership + +Permissions are separated from notifications and keyed by platform + permission type. + +## Storage Model Notes + +`FileStorage` holds object metadata and object key info for S3-compatible storage. +The storage repo generates presigned URLs and maps image sizes into frontend-friendly `domain.Photo` objects. + diff --git a/docs/development-workflows.md b/docs/development-workflows.md new file mode 100644 index 00000000..90147aa1 --- /dev/null +++ b/docs/development-workflows.md @@ -0,0 +1,80 @@ +# Development Workflows + +## Local Startup + +Primary commands (from `Makefile`): + +- `make init`: reset containers, build assets, seed data, start watch mode +- `make watch`: start process group via Overmind (`Procfile`) + +`Procfile` runs: + +- `watch-js` +- `watch-go` +- `watch-css` +- `watch-go-worker` + +## Services and Infra + +Docker Compose currently provisions: + +- Redis (`goship_cache`) +- Mailpit (`goship_mailpit`) + +Notes: + +- Postgres service is present but commented out in `docker-compose.yml`. +- Default config DB mode is embedded SQLite. + +## Assets + +JS build: + +- `npm run build` (via `build.mjs`) +- Bundles Svelte entrypoints and vanilla JS + +CSS build: + +- Tailwind CLI to `static/styles_bundle.css` + +## Database and Schema + +Entity schema source: + +- `ent/schema/*.go` + +Common workflow: + +1. `make ent-new name=YourEntity` (if new entity) +2. `make makemigrations name=your_change` +3. `make ent-gen` +4. `make migrate` + +## Worker and Tasks + +Run worker manually: + +- `make worker` + +Asynq UI: + +- `make workerui` + +Task processor registration: + +- `cmd/worker/main.go` + +## Testing + +Go tests: + +- `make test` +- `make cover` + +E2E tests: + +- `make e2e` +- `make e2eui` + +Note: current e2e specs are partially stale and should be treated as non-authoritative for GoShip behavior. + diff --git a/docs/http-routes.md b/docs/http-routes.md new file mode 100644 index 00000000..c1287a41 --- /dev/null +++ b/docs/http-routes.md @@ -0,0 +1,116 @@ +# HTTP Route Map + +Routes are registered in `pkg/routing/routes/router.go`. + +## Public/General Routes + +- `GET /` landing page +- `GET /up` healthcheck +- `GET /clear-cookie` +- `GET /about` +- `GET /privacy-policy` +- `GET /install-app` + +Docs pages (user-facing in-app docs): + +- `GET /docs` +- `GET /docs/gettingStarted` +- `GET /docs/guidedTour` +- `GET /docs/architecture` + +Email subscription: + +- `GET /emailSubscribe` +- `POST /emailSubscribe` +- `GET /email/subscription/:token` + +Service worker / app-links: + +- `GET /service-worker.js` +- `GET /.well-known/assetlinks.json` + +## User-Not-Authenticated Group (`/user`) + +- `GET /user/login` +- `POST /user/login` +- `GET /user/register` +- `POST /user/register` +- `GET /user/password` +- `POST /user/password` +- `GET /user/password/reset/token/:user/:password_token/:token` +- `POST /user/password/reset/token/:user/:password_token/:token` + +## Authenticated Onboarding Group (`/welcome`) + +- `GET /welcome/preferences` +- `GET /welcome/preferences/phone` +- `GET /welcome/preferences/phone/verification` +- `POST /welcome/preferences/phone/verification` +- `POST /welcome/preferences/phone/save` +- `GET /welcome/preferences/display-name/get` +- `POST /welcome/preferences/display-name/save` +- `GET /welcome/preferences/delete-account` +- `GET /welcome/preferences/delete-account/now` +- `GET /welcome/finish-onboarding` +- `GET /welcome/profileBio` +- `POST /welcome/profileBio/update` + +Notification subscription management during onboarding: + +- `GET /welcome/subscription/push` +- `POST /welcome/subscription/:platform` +- `DELETE /welcome/subscription/:platform` +- `GET /welcome/email-subscription/unsubscribe/:permission/:token` + +## Authenticated Group (`/auth`) + +- `GET /auth/logout` + +Fully onboarded-only routes (`/auth` with onboarding guard): + +- `GET /auth/homeFeed` +- `GET /auth/homeFeed/buttons` +- `GET /auth/profile` +- `GET /auth/uploadPhoto` +- `POST /auth/uploadPhoto` +- `DELETE /auth/uploadPhoto/:image_id` +- `GET /auth/currProfilePhoto` +- `POST /auth/currProfilePhoto` +- `GET /auth/notifications/normalNotificationsCount` +- `GET /auth/payments/get-public-key` +- `POST /auth/payments/create-checkout-session` +- `POST /auth/payments/create-portal-session` +- `GET /auth/payments/pricing` +- `GET /auth/payments/success` + +## Auth-Adjacent Routes + +- `GET /email/verify/:token` + +## External Integration Routes + +- `POST /Q2HBfAY7iid59J1SUN8h1Y3WxJcPWA/payments/webhooks` + +## Development-Only Error Preview Routes + +Registered only when not production: + +- `GET /error/400` +- `GET /error/401` +- `GET /error/403` +- `GET /error/404` +- `GET /error/500` + +## Routes Present But Not Currently Wired + +SSE route function exists but registration is commented out: + +- `GET /auth/realtime` + +Notification center routes have implementations but are commented out in route wiring: + +- list notifications +- mark all read +- delete notification +- mark read/unread endpoints + diff --git a/docs/known-gaps-and-risks.md b/docs/known-gaps-and-risks.md new file mode 100644 index 00000000..dd1a6e6e --- /dev/null +++ b/docs/known-gaps-and-risks.md @@ -0,0 +1,80 @@ +# Known Gaps and Risks + +This list is based on direct code inspection and is intended to guide contributor priorities. + +## 1) Container Initialization Mismatch (High) + +In `pkg/services/container.go`, `NewContainer()` does not call: + +- `initCache()` +- `initNotifier()` +- `initTasks()` + +Yet runtime code assumes these dependencies exist in multiple places, and `Shutdown()` calls `c.Tasks.Close()` and `c.Cache.Close()` unconditionally. + +Impact: + +- Potential nil-pointer panics on shutdown or during feature paths that rely on tasks/notifier/cache. +- Web and worker behavior can diverge from intended architecture. + +## 2) Realtime SSE Route Not Wired (High for realtime features) + +`pkg/routing/routes/realtime.go` has a full SSE handler, but router registration is commented out: + +- `sseRoutes(c, s, ctr)` is commented in `pkg/routing/routes/router.go`. + +Impact: + +- Realtime endpoint is effectively disabled in web runtime. + +## 3) Notification Center Endpoints Partially Disabled (Medium) + +Route handlers exist in `pkg/routing/routes/notifications.go`, but several are commented out during route wiring. + +Impact: + +- Notification center behavior is incomplete from an HTTP exposure perspective. +- Some notifier capabilities are not reachable from active routes. + +## 4) Stale/Inconsistent E2E Coverage (Medium) + +`e2e_tests/tests/goship.spec.ts` is marked with TODO and contains stale product/domain assumptions. + +Impact: + +- End-to-end test confidence for current GoShip behavior is limited. + +## 5) Dev Runtime Drift Between Config and Docker Compose (Medium) + +- Default config uses embedded SQLite (`config/config.yaml`). +- Docker Compose currently starts Redis and Mailpit only; DB service is commented out. +- Make targets include Postgres-dependent commands. + +Impact: + +- Contributors can experience confusion about canonical local dev DB path. + +## 6) In-App Docs Are Present But Sparse (Low) + +`/docs/*` pages exist, but architecture/getting-started sections are mostly placeholders. + +Impact: + +- Existing user-facing docs routes do not currently reflect true implementation depth. + +## 7) Some Feature Paths Still Use Placeholder Data (Low) + +Example: home feed button counts are hardcoded in `pkg/routing/routes/home_feed.go`. + +Impact: + +- UI may represent scaffolding rather than production data behavior in some sections. + +## Suggested Priority Order + +1. Fix container/service initialization and safe shutdown semantics. +2. Decide and document realtime strategy: wire SSE or remove dead path. +3. Re-enable or remove notification-center routes consistently. +4. Refresh e2e tests to match current GoShip flows. +5. Align local stack docs with actual DB mode and compose services. + diff --git a/docs/project-scope-analysis.md b/docs/project-scope-analysis.md new file mode 100644 index 00000000..ad21bae0 --- /dev/null +++ b/docs/project-scope-analysis.md @@ -0,0 +1,140 @@ +# Project Scope Analysis + +## What This Project Is + +GoShip is a Go + Echo + Templ + HTMX starter application that ships with: + +- Session-based authentication and account lifecycle flows +- Profile and onboarding flows +- Email subscriptions and transactional email support +- Subscription billing integration via Stripe +- Notification infrastructure (DB + push + SSE-oriented architecture) +- S3-compatible file storage support with image variants +- Background task processing (Asynq worker) +- Frontend asset bundling for Svelte components and vanilla JS + +The repository still carries heritage from a related product domain ("Cherie"), and some feature areas are partially wired or intentionally disabled. + +## Runtime Programs + +- `cmd/web/main.go`: main HTTP application server +- `cmd/worker/main.go`: asynchronous worker process for task handlers +- `cmd/seed/main.go`: seed runner for test/dev data + +## Feature Areas + +## 1) Authentication and Account Management + +Core flows implemented in routes and services: + +- Login/logout (`pkg/routing/routes/login.go`, `logout.go`) +- Register (`register.go`) +- Forgot/reset password (`forgot_password.go`, `reset_password.go`) +- Email verification (`verify_email.go`) +- Auth middleware and session handling (`pkg/middleware/auth.go`, `pkg/services/auth.go`) + +Key implementation choices: + +- Cookie-backed session auth using Gorilla sessions via Echo middleware. +- Password reset tokens stored as bcrypt hashes. +- Email verification tokens use JWT signed with app encryption key. + +## 2) Onboarding, Preferences, and Profile + +- Onboarding and preferences mostly in `pkg/routing/routes/preferences.go` +- Profile page in `profile.go` +- Mark onboarding completion (`/welcome/finish-onboarding`) +- Profile photo and gallery image routes (`profile_photo.go`, `upload_photo.go`) + +## 3) Payments and Subscription Lifecycle + +- Stripe checkout + customer portal + webhook in `pkg/routing/routes/payments.go` +- Local subscription state managed in `pkg/repos/subscriptions/subscriptions.go` +- Product model currently centered on free vs pro (`pkg/domain/enum.go`) + +Webhook flow currently handles: + +- `customer.subscription.created` +- `customer.subscription.updated` +- `customer.subscription.deleted` + +## 4) Notifications and Realtime Capabilities + +Implemented infrastructure includes: + +- Notification domain and storage +- Notification permissions by platform (push, fcm_push, email, sms) +- PWA and FCM push subscription storage/sending +- SSE pub/sub abstractions + +Status of exposure: + +- Some notification endpoints are active (count endpoint, permission/subscription management) +- Several notification-center routes are currently commented out in router wiring +- SSE route wiring is currently commented out in router (`sseRoutes` not enabled) + +## 5) Email Features + +- Newsletter-style email subscription flow (`email_subscribe.go`, `verify_email_subscription.go`) +- Task processor for subscription confirmation emails (`pkg/tasks/mail.go`) +- Update email sender integration (`emailsmanager`) +- Mail provider abstraction supports SMTP and Resend (`pkg/repos/mailer`) + +## 6) File Storage and Images + +- S3-compatible object storage through MinIO client (`pkg/repos/storage/storagerepo.go`) +- DB metadata persisted in `file_storages` +- Image size variants represented by enums and related image size records +- Signed URLs generated for image access + +## 7) Background Tasks + +Task processors under `pkg/tasks`: + +- Email subscription confirmation +- Email updates +- Subscription deactivation maintenance +- Daily conversation notification orchestration +- Stale notification cleanup + +Worker bootstrap and registration in `cmd/worker/main.go`. + +## 8) Frontend Delivery Model + +- Server-rendered pages via Templ (`templates/` + `pkg/controller`) +- HTMX-enhanced interactions +- Optional Svelte components bundled into `static/svelte_bundle.js` +- Optional vanilla JS bundle into `static/vanilla_bundle.js` + +Build pipeline: + +- JS via `build.mjs` + esbuild +- CSS via Tailwind CLI in Makefile + +## Environments and Configuration + +Config loading: + +- `config/config.go` + `config/config.yaml` +- Production can override via env vars (Viper env binding) + +Storage modes: + +- Embedded SQLite (default in config) +- Standalone Postgres path exists and includes pgvector extension setup + +## Testing Surface + +- 70+ Go tests in `pkg/**` +- Playwright e2e folder exists (`e2e_tests/`), but specs are currently product-domain stale and marked TODO + +## Operational Tooling + +- `Makefile` is the primary task runner (init, watch, test, migrations, worker) +- `Procfile` for multi-process dev with Overmind +- Docker Compose currently starts Redis + Mailpit; Postgres service is commented out + +## Practical Summary + +This codebase is a strong "production-ready starter" foundation with authentication, payments, notifications, storage, and worker primitives. It is also in an active transitional state where some features are scaffolded but not fully wired in the web runtime. + From d63c4ef46dcc08d07d2624a4380e66c9408cc45c Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 11:52:20 -0800 Subject: [PATCH 002/291] fix(services): make shutdown nil-safe and enforce pre-commit tests --- Makefile | 4 ++ README-framework-plan.md | 69 ++++++++++++++++++++++++++++++++++ lefthook.yml | 6 +++ pkg/services/container.go | 24 ++++++++---- pkg/services/container_test.go | 16 +++++++- scripts/precommit-tests.sh | 11 ++++++ 6 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 lefthook.yml create mode 100644 scripts/precommit-tests.sh diff --git a/Makefile b/Makefile index 02362156..7755b55b 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,10 @@ help: ## Show this help message. @echo @echo "To see the details of each command, run: make " +.PHONY: hooks +hooks: ## Install git hooks via lefthook + lefthook install + .PHONY: db db: ## Connect to the primary database docker exec -it goship_db psql postgresql://admin:admin@localhost:5432/goship_db diff --git a/README-framework-plan.md b/README-framework-plan.md index 1b930118..d91683b2 100644 --- a/README-framework-plan.md +++ b/README-framework-plan.md @@ -210,6 +210,35 @@ Target pyramid: 2. Integration tests (few): DB/repo boundaries and adapter contracts. 3. E2E tests (very few): key user journeys only. +### Pre-Commit Test Policy + +1. Every commit must pass `lefthook` pre-commit tests. +2. Pre-commit runs a fast, stateless Go unit suite only. +3. Docker/integration suites run separately (manual or CI), not as a local pre-commit default. +4. As packages are refactored, they should be moved into the pre-commit suite. + +## Commit Standard + +Use Conventional Commits for all framework work: + +`type(scope): imperative summary` + +Allowed types: + +- `feat` +- `fix` +- `refactor` +- `test` +- `docs` +- `chore` +- `ci` + +Examples: + +- `fix(services): make container shutdown nil-safe` +- `test(services): add nil-safe shutdown coverage` +- `docs(plan): define first rework execution workflow` + ## Module Plan Proposed module boundaries: @@ -231,6 +260,46 @@ Proposed module boundaries: 3. Clean and align current docs with actual runtime behavior. 4. Refresh stale e2e coverage for critical flows. +## First Rework Execution Plan + +This section is the active implementation tracker for the first rework pass. +Rule: execute exactly one task at a time, validate with tests, then move to the next task. + +### Quality Gates (for every task) + +1. Add or update tests with the change (prefer table-driven tests). +2. Run targeted tests for touched package(s). +3. Keep tests mostly stateless (no Docker for default test path). +4. No task is marked complete without test evidence. + +### Coverage Targets + +1. Global target: 90%+ over time (not required in one PR). +2. Reworked packages should trend toward 90%+ before moving on. +3. Complex pure-logic packages should aim for near-100% branch coverage with table tests. + +### Active Task Queue (First Rework) + +1. `R0.1` Container shutdown safety + reliable container unit test baseline. +Status: `completed` +Done when: +- `Container.Shutdown()` is nil-safe for optional services. +- container unit tests compile and pass without external services. +Test evidence: +- `go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$'` + +2. `R0.2` Container initialization policy by runtime mode (`single-node` vs `distributed`), with explicit config contract. +Status: `in_progress` + +3. `R0.3` Router consistency pass (realtime + notifications wired consistently with initialized dependencies). +Status: `not_started` + +4. `R0.4` Testing harness improvements so default `make test` is Docker-free and fast. +Status: `not_started` + +5. `R0.5` Establish package-level coverage baselines and close highest-value test gaps. +Status: `not_started` + ### Phase 1: Core Abstractions 1. Define `core` interfaces for DB/cache/pubsub/jobs/storage. diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 00000000..2ff541db --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,6 @@ +pre-commit: + parallel: false + commands: + unit-tests: + run: bash scripts/precommit-tests.sh + diff --git a/pkg/services/container.go b/pkg/services/container.go index 40bb94c6..120c1ccd 100644 --- a/pkg/services/container.go +++ b/pkg/services/container.go @@ -106,17 +106,25 @@ func NewContainer() *Container { // Shutdown shuts the Container down and disconnects all connections func (c *Container) Shutdown() error { - if err := c.Tasks.Close(); err != nil { - return err + if c.Tasks != nil { + if err := c.Tasks.Close(); err != nil { + return err + } } - if err := c.Cache.Close(); err != nil { - return err + if c.Cache != nil { + if err := c.Cache.Close(); err != nil { + return err + } } - if err := c.ORM.Close(); err != nil { - return err + if c.ORM != nil { + if err := c.ORM.Close(); err != nil { + return err + } } - if err := c.Database.Close(); err != nil { - return err + if c.Database != nil { + if err := c.Database.Close(); err != nil { + return err + } } return nil diff --git a/pkg/services/container_test.go b/pkg/services/container_test.go index ce0d760f..a42c9c21 100644 --- a/pkg/services/container_test.go +++ b/pkg/services/container_test.go @@ -4,16 +4,28 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewContainer(t *testing.T) { + c := NewContainer() + t.Cleanup(func() { + require.NoError(t, c.Shutdown()) + }) + assert.NotNil(t, c.Web) assert.NotNil(t, c.Config) assert.NotNil(t, c.Validator) - assert.NotNil(t, c.Cache) assert.NotNil(t, c.Database) assert.NotNil(t, c.ORM) assert.NotNil(t, c.Mail) assert.NotNil(t, c.Auth) - assert.NotNil(t, c.Tasks) + assert.Nil(t, c.Cache) + assert.Nil(t, c.Tasks) + assert.Nil(t, c.Notifier) +} + +func TestContainerShutdownNilSafe(t *testing.T) { + c := &Container{} + assert.NoError(t, c.Shutdown()) } diff --git a/scripts/precommit-tests.sh b/scripts/precommit-tests.sh new file mode 100644 index 00000000..42d8ee45 --- /dev/null +++ b/scripts/precommit-tests.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Running stateless pre-commit Go tests..." + +go test ./config ./pkg/context ./pkg/funcmap ./pkg/htmx ./pkg/repos/msg +go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$' + +echo "Pre-commit test suite passed." + From 842d6e5f5cea107a7434a85dffbaedae7d0d9868 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 12:16:54 -0800 Subject: [PATCH 003/291] feat(runtime): add profile/process config and runtime plan resolver --- README-framework-plan.md | 152 +++++++++++++++++++++++++++++++++-- config/config.go | 31 +++++++ config/config.yaml | 15 ++++ config/config_test.go | 4 + pkg/runtimeplan/plan.go | 67 +++++++++++++++ pkg/runtimeplan/plan_test.go | 129 +++++++++++++++++++++++++++++ scripts/precommit-tests.sh | 5 +- 7 files changed, 395 insertions(+), 8 deletions(-) create mode 100644 pkg/runtimeplan/plan.go create mode 100644 pkg/runtimeplan/plan_test.go diff --git a/README-framework-plan.md b/README-framework-plan.md index d91683b2..1b83b121 100644 --- a/README-framework-plan.md +++ b/README-framework-plan.md @@ -20,6 +20,71 @@ Build a highly productive, convention-first Go framework where developers can: 2. install batteries as versioned modules; 3. choose deployment/runtime mode without rewriting app code. +Primary framing: + +- GoShip aims to be a Ruby on Rails alternative in Go, with comparable batteries-included productivity and developer ergonomics. +- Product aspiration: be a deeply loved framework by developers by putting developer joy, speed, and clarity first. +- Rails inspiration applies to the entire framework experience (not only environment settings): app structure, conventions, generators, batteries, defaults, testing, and deployment workflows. + +### Rails-Inspired Framework Pillars + +1. Convention over configuration: +- opinionated defaults for layout, modules, and naming; +- explicit escape hatches when teams need control. +2. Generators-first workflow: +- create apps/resources/jobs/modules with minimal manual wiring. +3. Batteries-included, modular delivery: +- auth, billing, notifications, storage, admin, jobs as installable modules. +4. Stable abstractions over pluggable implementations: +- developers code to GoShip interfaces; adapters are swap-friendly. +5. Excellent local DX: +- fast feedback, minimal setup, stateless tests by default. +6. Production-ready path: +- clear migration from local single-process to distributed process topology. + +Rails inspiration for configuration and "what runs": + +1. Rails uses layered configuration: +- `config/application.rb` for global app config; +- `config/environments/*.rb` for per-environment overrides; +- `config/initializers/*.rb` for subsystem wiring; +- Gemfile/Bundler groups for dependency activation. +2. GoShip equivalent target: +- global framework/module manifest + adapter selection; +- per-environment overrides; +- startup wiring that follows enabled modules/adapters. + +### Execution Topology and File Approach + +To decide what runs in parallel (web/worker/scheduler) and with which adapters, use layered config files: + +1. `config/application.yaml`: +- global defaults; +- module enablement; +- adapter defaults (`db`, `cache`, `jobs`, `pubsub`, `storage`). +2. `config/environments/{local,dev,test,prod}.yaml`: +- per-environment overrides; +- profile selection (`server-db`, `single-node`, `distributed`). +3. `config/processes.yaml`: +- process topology matrix: + - `web` (bool) + - `worker` (bool) + - `scheduler` (bool) + - `co_located` (bool) +- examples: + - local default: web=true, worker=true, scheduler=true (single process with goroutines if backend supports it) + - prod distributed web: web=true, worker=false, scheduler=false + - prod worker: web=false, worker=true, scheduler=true +4. `config/initializers/*.go` (generated/wired by CLI): +- runtime boot wiring based on enabled modules/adapters/processes. + +CLI responsibilities: + +1. `goship new` writes initial config set with sane defaults. +2. `goship profile set ` updates environment/process presets. +3. `goship module add ` updates module manifest + initializer wiring. +4. `goship jobs backend set ` updates adapter config with capability checks. + ## Core Product Goals 1. Rails-like productivity in Go. @@ -34,6 +99,8 @@ Build a highly productive, convention-first Go framework where developers can: 2. Use a monorepo with multiple Go modules plus `go.work` for maintainers. 3. Ship installable/versioned modules (auth, billing, notifications, storage, admin). 4. Keep one blessed default stack, but support both single-node and distributed runtime modes via adapters. +5. Near-term default is database-server-first (Postgres first), not SQLite-centric. +6. Redis is optional capability, not a hard requirement. ## Upstream/Downstream Relationship @@ -61,6 +128,38 @@ Based on Cherie docs and current implementation, these are strong candidates to - cache invalidation/ops guidance; - migration caveats and guardrails. +## Pagoda Upstream Intake Plan + +Long-term policy: + +1. Treat Pagoda as an upstream source of framework/runtime improvements. +2. Regularly evaluate and selectively port changes into GoShip. +3. Do not adopt Pagoda UI/component layer choices that conflict with GoShip direction. + +Current known upstream shifts in Pagoda (to evaluate and/or port): + +1. Default move from Postgres+Redis to SQLite-centric operation (reference only, not the current GoShip default direction). +2. Migration from Asynq to Backlite for DB-backed task queues. +3. In-process task runner startup in web process, with graceful task shutdown in container. +4. Use of in-memory cache as default for simpler local development. +5. Admin/task runtime integration improvements. + +Non-goals for direct adoption: + +1. Go-based HTML component stack from Pagoda (`gomponents`) as a hard dependency. +2. Any upstream UI architecture changes that reduce GoShip's Templ+HTMX ergonomics. +3. Forcing GoShip into SQLite-centric defaults at this stage. + +## Pagoda Intake TODOs + +- [ ] Create a recurring upstream review cadence (weekly or per-tag) for Pagoda. +- [ ] Add a "Pagoda intake log" mapping upstream commit/tag -> GoShip decision (`adopt`, `adapt`, `skip`). +- [ ] Evaluate Backlite-style DB-backed jobs as a GoShip jobs adapter candidate. +- [ ] Port container lifecycle hardening patterns where applicable (startup/shutdown ordering and timeouts). +- [ ] Port testability improvements that reduce Docker dependence. +- [ ] Keep UI/component layer decisions independent from runtime/service layer intake. +- [ ] Prefer LLM-assisted feature re-implementation over direct commit cherry-picks due codebase divergence. + ## Cherie Sync Policy 1. Every GoShip framework milestone must include a Cherie compatibility check. @@ -77,19 +176,52 @@ Based on Cherie docs and current implementation, these are strong candidates to ## Runtime Modes -### 1) Single-Node Mode +### 1) Server-DB Mode (Primary Near-Term Default) + +- DB: external DB server (Postgres first; MySQL later through adapter boundary) +- Cache: in-memory by default +- Jobs: pluggable (`inproc` for simplicity, durable backend for reliability) +- Redis: optional, not required + +### 2) Single-Node Mode (Future-Friendly Profile) - DB: SQLite - Cache: in-memory - PubSub: in-process - Jobs: in-process scheduler/worker -### 2) Distributed Mode +### 3) Distributed Mode - DB: Postgres -- Cache: Redis -- PubSub: Redis-backed -- Jobs: Asynq +- Cache: adapter-driven (Redis optional) +- PubSub: adapter-driven +- Jobs: adapter-driven (DB-backed queue or external queue service) + +## Worker Queue Abstraction Strategy + +Goal: + +- one stable app-facing jobs API with multiple backend implementations. + +Design principles: + +1. Define a minimal stable core contract in `goship/jobs`: +- `Register(name, handler)` +- `Enqueue(name, payload, opts...)` +- `StartWorker(ctx)` +- `StopWorker(ctx)` +- `StartScheduler(ctx)` (if supported) +2. Use capability declarations per backend (delayed jobs, retries, cron, priority, dead-letter, UI). +3. Validate feature usage against backend capabilities at startup. +4. Keep backend-specific settings in adapter config, not spread in app code. +5. Keep handlers/payload contracts backend-agnostic. + +Planned adapters: + +1. `jobs/inproc` (best local DX) +2. `jobs/dbqueue` (DB-backed durable queue, no Redis required) +3. `jobs/asynq` (optional Redis-backed adapter) +4. future cloud adapters (Pub/Sub / Cloud Tasks bridges) ## Required Core Interfaces @@ -289,10 +421,16 @@ Test evidence: - `go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$'` 2. `R0.2` Container initialization policy by runtime mode (`single-node` vs `distributed`), with explicit config contract. -Status: `in_progress` +Status: `completed` +Done when: +- runtime/process/adapters config scaffold exists; +- runtime plan resolver exists with table tests; +- no startup behavior change yet (scaffold only). +Test evidence: +- `go test ./config ./pkg/runtimeplan` 3. `R0.3` Router consistency pass (realtime + notifications wired consistently with initialized dependencies). -Status: `not_started` +Status: `in_progress` 4. `R0.4` Testing harness improvements so default `make test` is Docker-free and fast. Status: `not_started` diff --git a/config/config.go b/config/config.go index 0ea3341c..10e15e01 100644 --- a/config/config.go +++ b/config/config.go @@ -24,6 +24,7 @@ const ( type app string type environment string type dbmode string +type runtimeprofile string const ( // EnvLocal represents the local environment @@ -49,6 +50,15 @@ const ( // DBModeStandalone represents a standalone DB as being used as a storage backend DBModeStandalone dbmode = "standalone" + + // RuntimeProfileServerDB is the default profile using an external DB server. + RuntimeProfileServerDB runtimeprofile = "server-db" + + // RuntimeProfileSingleNode is the profile targeting single-node embedding. + RuntimeProfileSingleNode runtimeprofile = "single-node" + + // RuntimeProfileDistributed is the profile targeting distributed processes. + RuntimeProfileDistributed runtimeprofile = "distributed" ) // SwitchEnvironment sets the environment variable used to dictate which environment the application is @@ -65,6 +75,9 @@ type ( Config struct { HTTP HTTPConfig App AppConfig + Runtime RuntimeConfig + Processes ProcessesConfig + Adapters AdaptersConfig Cache CacheConfig Database DatabaseConfig Mail MailConfig @@ -73,6 +86,24 @@ type ( Storage StorageConfig } + RuntimeConfig struct { + Profile runtimeprofile + } + + ProcessesConfig struct { + Web bool + Worker bool + Scheduler bool + CoLocated bool + } + + AdaptersConfig struct { + DB string + Cache string + Jobs string + PubSub string + } + // HTTPConfig stores HTTP configuration HTTPConfig struct { Hostname string diff --git a/config/config.yaml b/config/config.yaml index 2c2d053a..cc4d2bc8 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -49,6 +49,21 @@ app: appEncryptionKey: "=" firebaseBase64AccessKeys: "" +runtime: + profile: "server-db" + +processes: + web: true + worker: false + scheduler: false + coLocated: false + +adapters: + db: "postgres" + cache: "memory" + jobs: "inproc" + pubsub: "inproc" + cache: hostname: "localhost" port: 6379 diff --git a/config/config_test.go b/config/config_test.go index 76c80951..545062ee 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -14,4 +14,8 @@ func TestGetConfig(t *testing.T) { cfg, err := GetConfig() require.NoError(t, err) assert.Equal(t, EnvLocal, cfg.App.Environment) + assert.Equal(t, RuntimeProfileServerDB, cfg.Runtime.Profile) + assert.True(t, cfg.Processes.Web) + assert.Equal(t, "postgres", cfg.Adapters.DB) + assert.Equal(t, "inproc", cfg.Adapters.Jobs) } diff --git a/pkg/runtimeplan/plan.go b/pkg/runtimeplan/plan.go new file mode 100644 index 00000000..a7b068ab --- /dev/null +++ b/pkg/runtimeplan/plan.go @@ -0,0 +1,67 @@ +package runtimeplan + +import ( + "fmt" + + "github.com/mikestefanello/pagoda/config" +) + +// Plan describes the resolved runtime/process topology without mutating startup behavior. +type Plan struct { + Profile string + RunWeb bool + RunWorker bool + RunScheduler bool + CoLocated bool + Adapters Adapters +} + +// Adapters contains selected backend implementations for major pluggable capabilities. +type Adapters struct { + DB string + Cache string + Jobs string + PubSub string +} + +// Resolve builds a normalized runtime plan from config and validates obvious topology mistakes. +func Resolve(cfg *config.Config) (Plan, error) { + if cfg == nil { + return Plan{}, fmt.Errorf("nil config") + } + + profile := string(cfg.Runtime.Profile) + switch cfg.Runtime.Profile { + case "", config.RuntimeProfileServerDB: + profile = string(config.RuntimeProfileServerDB) + case config.RuntimeProfileSingleNode, config.RuntimeProfileDistributed: + default: + return Plan{}, fmt.Errorf("unknown runtime profile: %s", cfg.Runtime.Profile) + } + + p := Plan{ + Profile: profile, + RunWeb: cfg.Processes.Web, + RunWorker: cfg.Processes.Worker, + RunScheduler: cfg.Processes.Scheduler, + CoLocated: cfg.Processes.CoLocated, + Adapters: Adapters{ + DB: cfg.Adapters.DB, + Cache: cfg.Adapters.Cache, + Jobs: cfg.Adapters.Jobs, + PubSub: cfg.Adapters.PubSub, + }, + } + + if !p.RunWeb && !p.RunWorker && !p.RunScheduler { + return Plan{}, fmt.Errorf("invalid processes: at least one of web/worker/scheduler must be enabled") + } + + // Capability guardrails for current scaffold. + if cfg.Runtime.Profile == config.RuntimeProfileDistributed && p.Adapters.Jobs == "inproc" { + return Plan{}, fmt.Errorf("invalid distributed jobs backend: inproc") + } + + return p, nil +} + diff --git a/pkg/runtimeplan/plan_test.go b/pkg/runtimeplan/plan_test.go new file mode 100644 index 00000000..e22ddf4c --- /dev/null +++ b/pkg/runtimeplan/plan_test.go @@ -0,0 +1,129 @@ +package runtimeplan + +import ( + "testing" + + "github.com/mikestefanello/pagoda/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolve(t *testing.T) { + tests := []struct { + name string + cfg *config.Config + wantErr string + assertion func(t *testing.T, p Plan) + }{ + { + name: "server db defaults and web process", + cfg: &config.Config{ + Runtime: config.RuntimeConfig{ + Profile: config.RuntimeProfileServerDB, + }, + Processes: config.ProcessesConfig{ + Web: true, + }, + Adapters: config.AdaptersConfig{ + DB: "postgres", + Jobs: "inproc", + Cache: "memory", + }, + }, + assertion: func(t *testing.T, p Plan) { + assert.Equal(t, "server-db", p.Profile) + assert.True(t, p.RunWeb) + assert.False(t, p.RunWorker) + assert.Equal(t, "postgres", p.Adapters.DB) + }, + }, + { + name: "empty profile normalizes to server db", + cfg: &config.Config{ + Processes: config.ProcessesConfig{ + Web: true, + }, + Adapters: config.AdaptersConfig{ + Jobs: "inproc", + }, + }, + assertion: func(t *testing.T, p Plan) { + assert.Equal(t, "server-db", p.Profile) + }, + }, + { + name: "invalid when no processes enabled", + cfg: &config.Config{ + Runtime: config.RuntimeConfig{ + Profile: config.RuntimeProfileServerDB, + }, + }, + wantErr: "at least one of web/worker/scheduler must be enabled", + }, + { + name: "invalid profile", + cfg: &config.Config{ + Runtime: config.RuntimeConfig{ + Profile: "unknown-profile", + }, + Processes: config.ProcessesConfig{ + Web: true, + }, + }, + wantErr: "unknown runtime profile", + }, + { + name: "distributed rejects inproc jobs", + cfg: &config.Config{ + Runtime: config.RuntimeConfig{ + Profile: config.RuntimeProfileDistributed, + }, + Processes: config.ProcessesConfig{ + Web: true, + }, + Adapters: config.AdaptersConfig{ + Jobs: "inproc", + }, + }, + wantErr: "invalid distributed jobs backend", + }, + { + name: "distributed accepts durable jobs backend", + cfg: &config.Config{ + Runtime: config.RuntimeConfig{ + Profile: config.RuntimeProfileDistributed, + }, + Processes: config.ProcessesConfig{ + Web: true, + Worker: true, + CoLocated: true, + }, + Adapters: config.AdaptersConfig{ + Jobs: "dbqueue", + }, + }, + assertion: func(t *testing.T, p Plan) { + assert.Equal(t, "distributed", p.Profile) + assert.True(t, p.RunWorker) + assert.True(t, p.CoLocated) + assert.Equal(t, "dbqueue", p.Adapters.Jobs) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + plan, err := Resolve(tt.cfg) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + require.NotNil(t, tt.assertion) + tt.assertion(t, plan) + }) + } +} + diff --git a/scripts/precommit-tests.sh b/scripts/precommit-tests.sh index 42d8ee45..c7cc1312 100644 --- a/scripts/precommit-tests.sh +++ b/scripts/precommit-tests.sh @@ -4,8 +4,11 @@ set -euo pipefail echo "Running stateless pre-commit Go tests..." +mkdir -p .cache/go-build +export GOCACHE="$(pwd)/.cache/go-build" + go test ./config ./pkg/context ./pkg/funcmap ./pkg/htmx ./pkg/repos/msg go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$' +go test ./pkg/runtimeplan echo "Pre-commit test suite passed." - From 83b08acd1a39aa6e36e1c9b263d8ceaa23e427e4 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 12:17:35 -0800 Subject: [PATCH 004/291] chore(repo): ignore local go build cache directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ff8a5e59..62f1a5ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Go ------------------------------------------------------------------------------------------ .idea tmp/ +.cache/ schemaspy-output .DS_Store .env @@ -191,4 +192,4 @@ bubblewrap deploy-real.yml -.kamal/secrets \ No newline at end of file +.kamal/secrets From 179d70df14d79d8ab28bdeb2e33a2cef4ce0dba2 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 13:55:44 -0800 Subject: [PATCH 005/291] refactor(build): simplify make targets and add make dev workflow --- Makefile | 71 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 7755b55b..141b5acc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,12 @@ +SHELL := /bin/bash +.DEFAULT_GOAL := help + # Define variables PGVECTOR_IMAGE_NAME = custom-pgvector-for-atlas PGVECTOR_IMAGE_TAG = latest PGVECTOR_IMAGE_DIR = pgvector-image +NPM ?= npm +TAILWIND ?= npx tailwindcss # Define a function to check for docker compose command define find_docker_compose @@ -14,13 +19,8 @@ define find_docker_compose fi endef -# TODO: https://github.com/casey/just?tab=readme-ov-file seems like a nice alternative/improvement to make # Determine if you have docker-compose or docker compose installed locally -# If this does not work on your system, just set the name of the executable you have installed DCO_BIN := $(shell $(find_docker_compose)) -define Comment - - Run `make help` to see all the available options. -endef .PHONY: help help: ## Show this help message. @@ -30,10 +30,25 @@ help: ## Show this help message. @echo @echo "To see the details of each command, run: make " +.PHONY: ensure-compose +ensure-compose: + @if [ -z "$(DCO_BIN)" ]; then \ + echo "No docker compose command found (docker-compose or docker compose)."; \ + exit 1; \ + fi + .PHONY: hooks hooks: ## Install git hooks via lefthook lefthook install +# Core workflow ------------------------------------------------------------------------------ + +.PHONY: dev +dev: up deps-js build-js build-css watch ## Start local development (infra + asset deps + watch mode) + +.PHONY: dev-reset +dev-reset: reset deps-js build-js build-css seed watch ## Full reset then start dev (destructive to local DB state) + .PHONY: db db: ## Connect to the primary database docker exec -it goship_db psql postgresql://admin:admin@localhost:5432/goship_db @@ -125,16 +140,16 @@ generate: ## Run code generation go generate ./... .PHONY: up -up: ## Start the Docker containers +up: ensure-compose ## Start Docker containers $(DCO_BIN) up -d --remove-orphans sleep 3 .PHONY: down -down: ## Stop the Docker containers +down: ensure-compose ## Stop Docker containers $(DCO_BIN) down -.PHONY: down -down-volume: ## Stop the Docker containers +.PHONY: down-volume +down-volume: ensure-compose ## Stop Docker containers and delete volumes $(DCO_BIN) down --volumes .PHONY: seed @@ -142,42 +157,38 @@ seed: ## Seed with data (must be clean to begin with or will die) go run cmd/seed/main.go .PHONY: reset -reset: ## Rebuild Docker containers to wipe all data - $(DCO_BIN) down - make up +reset: down up ## Rebuild Docker containers to wipe all data -.PHONY: init -init: ## Set up the repo and run a fully working version of GoShip - make reset - make build-js - make build-css - make seed - make watch +.PHONY: init +init: dev-reset ## Backward-compatible alias for full reset dev startup .PHONY: build-js build-js: ## Build JS/Svelte assets - npm run build + $(NPM) run build +.PHONY: deps-js +deps-js: ## Install JS dependencies + $(NPM) install -.PHONY: build-js -watch-js: ## Build JS/Svelte assets (auto reload changes) - npm install - npm run watch +.PHONY: watch-js +watch-js: ## Watch and rebuild JS/Svelte assets + $(NPM) run watch +.PHONY: build-css build-css: ## Build CSS assets (auto reload changes) - npx tailwindcss -i ./styles/styles.css -o ./static/styles_bundle.css + $(TAILWIND) -i ./styles/styles.css -o ./static/styles_bundle.css +.PHONY: watch-css watch-css: ## Build CSS assets (auto reload changes) - npx tailwindcss -i ./styles/styles.css -o ./static/styles_bundle.css --watch - + $(TAILWIND) -i ./styles/styles.css -o ./static/styles_bundle.css --watch -.PHONY: run +.PHONY: watch-go watch-go: ## Run the application with air (auto reload changes) clear air -watch: - echo "Run 'nvm use v18.20.7' to make sure the JS version will work." +watch: ## Start all dev watchers/processes through overmind + @echo "Tip: run 'nvm use v18.20.7' if JS tooling fails." overmind start .PHONY: test From eaf5f0e94877e8dbd7cecbcb5f7443c316f8099f Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 13:55:51 -0800 Subject: [PATCH 006/291] feat(routing): gate cache and realtime routes by runtime dependencies --- README-framework-plan.md | 59 ++++++++++++++++++++++- pkg/routing/routes/router.go | 28 ++++++++++- pkg/runtimeplan/features.go | 20 ++++++++ pkg/runtimeplan/features_test.go | 80 ++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 pkg/runtimeplan/features.go create mode 100644 pkg/runtimeplan/features_test.go diff --git a/README-framework-plan.md b/README-framework-plan.md index 1b83b121..a7975f7f 100644 --- a/README-framework-plan.md +++ b/README-framework-plan.md @@ -93,6 +93,32 @@ CLI responsibilities: 4. Modular infrastructure adapters. 5. Strong defaults with optional escape hatches. +## Architecture Style (Pragmatic) + +GoShip will use a pragmatic blend: + +1. Rails-style developer experience at the framework surface. +2. Selective clean-architecture boundaries only where they add real value. + +What this means: + +1. Do not implement clean architecture verbatim across every layer. +2. Use interfaces at infrastructure seams so backend technology is swappable. +3. Keep core app/domain logic straightforward and low-ceremony. + +Primary infrastructure seams to abstract: + +1. `Store` (database) +2. `Cache` +3. `Jobs` +4. `PubSub` +5. `BlobStorage` +6. `Mailer` + +Non-goal: + +- Avoid abstracting everything "just in case"; abstractions must improve portability or testability. + ## Confirmed Decisions 1. Keep Ent as the ORM for now. @@ -215,6 +241,7 @@ Design principles: 3. Validate feature usage against backend capabilities at startup. 4. Keep backend-specific settings in adapter config, not spread in app code. 5. Keep handlers/payload contracts backend-agnostic. +6. Introduce capability contracts so unsupported backend features fail fast at startup. Planned adapters: @@ -223,6 +250,16 @@ Planned adapters: 3. `jobs/asynq` (optional Redis-backed adapter) 4. future cloud adapters (Pub/Sub / Cloud Tasks bridges) +## Backend-Agnostic Framework Rule + +Like Rails/Django, GoShip should not force one database/cache choice. + +Policy: + +1. Framework APIs remain backend-agnostic. +2. Backend selection happens in config + runtime wiring. +3. Application/business code must not depend directly on concrete infra clients. + ## Required Core Interfaces Create stable contracts in `core` so app code is backend-agnostic: @@ -349,6 +386,16 @@ Target pyramid: 3. Docker/integration suites run separately (manual or CI), not as a local pre-commit default. 4. As packages are refactored, they should be moved into the pre-commit suite. +### Agent-Driven Documentation and Downstream Sync Policy + +For every implementation change and commit: + +1. Update developer + LLM-oriented docs in the same change stream. +2. Keep docs split into focused markdown files by area/topic rather than one giant file. +3. Reflect behavior changes in framework docs and LLM reference docs. +4. Add/update downstream impact notes for Cherie when GoShip changes affect integration. +5. Treat documentation sync and Cherie sync as required agent checks. + ## Commit Standard Use Conventional Commits for all framework work: @@ -430,10 +477,16 @@ Test evidence: - `go test ./config ./pkg/runtimeplan` 3. `R0.3` Router consistency pass (realtime + notifications wired consistently with initialized dependencies). -Status: `in_progress` +Status: `completed` +Done when: +- router enables cache middleware only when cache dependency is available; +- realtime routes are wired only when notifier+pubsub dependencies are available; +- runtime plan is resolved at router build with safe fallback on invalid plan configuration. +Test evidence: +- `go test ./pkg/runtimeplan` 4. `R0.4` Testing harness improvements so default `make test` is Docker-free and fast. -Status: `not_started` +Status: `in_progress` 5. `R0.5` Establish package-level coverage baselines and close highest-value test gaps. Status: `not_started` @@ -487,6 +540,8 @@ Status: `not_started` - [ ] Create a `CHERIE_SYNC.md` runbook (upgrade process + rollback + validation checklist). - [ ] Create a baseline compatibility test suite for Cherie critical paths. - [ ] Define testing standards doc: what must be unit-testable and where table tests are required. +- [ ] Add doc-sync guardrails in pre-commit/CI for framework-impacting changes. +- [ ] Add Cherie-sync guardrails in pre-commit/CI (or mandatory checklist gate). ## Near-Term diff --git a/pkg/routing/routes/router.go b/pkg/routing/routes/router.go index a93f376c..44b73755 100644 --- a/pkg/routing/routes/router.go +++ b/pkg/routing/routes/router.go @@ -14,6 +14,7 @@ import ( "github.com/mikestefanello/pagoda/pkg/repos/emailsmanager" "github.com/mikestefanello/pagoda/pkg/repos/notifierrepo" "github.com/mikestefanello/pagoda/pkg/repos/profilerepo" + "github.com/mikestefanello/pagoda/pkg/runtimeplan" storagerepo "github.com/mikestefanello/pagoda/pkg/repos/storage" "github.com/mikestefanello/pagoda/pkg/repos/subscriptions" routeNames "github.com/mikestefanello/pagoda/pkg/routing/routenames" @@ -35,6 +36,21 @@ func sseSkipper(c echo.Context) bool { // BuildRouter builds the router func BuildRouter(c *services.Container) { + plan, err := runtimeplan.Resolve(c.Config) + if err != nil { + log.Warn(). + Err(err). + Msg("invalid runtime plan configuration, falling back to safe web defaults") + plan = runtimeplan.Plan{ + Profile: string(c.Config.Runtime.Profile), + RunWeb: true, + Adapters: runtimeplan.Adapters{ + PubSub: c.Config.Adapters.PubSub, + }, + } + } + webFeatures := runtimeplan.ResolveWebFeatures(plan, c.Cache != nil, c.Notifier != nil) + // Create a slog logger, which: // - Logs to json. // TODO: add option to log to file @@ -112,7 +128,6 @@ func BuildRouter(c *services.Container) { }), session.Middleware(sessions.NewCookieStore([]byte(c.Config.App.EncryptionKey))), middleware.LoadAuthenticatedUser(c.Auth, profileRepo, subscriptionsRepo), - // middleware.ServeCachedPage(c.Cache), // NOTE: turn on if you use a cache echomw.CSRFWithConfig(echomw.CSRFConfig{ TokenLookup: "form:csrf,header:X-CSRF-Token,query:csrf", CookieMaxAge: 172800, // 48h @@ -123,6 +138,11 @@ func BuildRouter(c *services.Container) { }), middleware.SetDeviceTypeToServe(), ) + if webFeatures.EnablePageCache { + g.Use(middleware.ServeCachedPage(c.Cache)) + } else { + log.Info().Msg("page cache middleware disabled (cache dependency unavailable or web process disabled)") + } // Realtime routes router s.Use( @@ -188,7 +208,11 @@ func BuildRouter(c *services.Container) { if c.Config.App.OperationalConstants.UserSignupEnabled { coreAuthRoutes(c, g, ctr) - // sseRoutes(c, s, ctr) + if webFeatures.EnableRealtime { + sseRoutes(c, s, ctr) + } else { + log.Info().Msg("realtime SSE routes disabled (notifier/pubsub dependency unavailable)") + } externalRoutes(c, e, ctr) } diff --git a/pkg/runtimeplan/features.go b/pkg/runtimeplan/features.go new file mode 100644 index 00000000..c96a1700 --- /dev/null +++ b/pkg/runtimeplan/features.go @@ -0,0 +1,20 @@ +package runtimeplan + +// WebFeatures describes runtime-dependent feature exposure for web routing. +type WebFeatures struct { + EnablePageCache bool + EnableRealtime bool +} + +// ResolveWebFeatures computes web feature flags from runtime plan and available dependencies. +func ResolveWebFeatures(plan Plan, hasCache, hasNotifier bool) WebFeatures { + if !plan.RunWeb { + return WebFeatures{} + } + + return WebFeatures{ + EnablePageCache: hasCache, + EnableRealtime: hasNotifier && plan.Adapters.PubSub != "", + } +} + diff --git a/pkg/runtimeplan/features_test.go b/pkg/runtimeplan/features_test.go new file mode 100644 index 00000000..f46ec141 --- /dev/null +++ b/pkg/runtimeplan/features_test.go @@ -0,0 +1,80 @@ +package runtimeplan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResolveWebFeatures(t *testing.T) { + tests := []struct { + name string + plan Plan + hasCache bool + hasNotifier bool + want WebFeatures + }{ + { + name: "web disabled disables all web features", + plan: Plan{ + RunWeb: false, + }, + hasCache: true, + hasNotifier: true, + want: WebFeatures{}, + }, + { + name: "page cache enabled when web runs and cache exists", + plan: Plan{ + RunWeb: true, + Adapters: Adapters{ + PubSub: "inproc", + }, + }, + hasCache: true, + hasNotifier: false, + want: WebFeatures{ + EnablePageCache: true, + EnableRealtime: false, + }, + }, + { + name: "realtime enabled when notifier and pubsub are present", + plan: Plan{ + RunWeb: true, + Adapters: Adapters{ + PubSub: "redis", + }, + }, + hasCache: false, + hasNotifier: true, + want: WebFeatures{ + EnablePageCache: false, + EnableRealtime: true, + }, + }, + { + name: "realtime disabled without pubsub adapter", + plan: Plan{ + RunWeb: true, + Adapters: Adapters{ + PubSub: "", + }, + }, + hasCache: true, + hasNotifier: true, + want: WebFeatures{ + EnablePageCache: true, + EnableRealtime: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ResolveWebFeatures(tt.plan, tt.hasCache, tt.hasNotifier) + assert.Equal(t, tt.want, got) + }) + } +} + From fdc3d6566c100789fd03e7c6960087b562aa865f Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 14:01:13 -0800 Subject: [PATCH 007/291] refactor(dev): move startup flow into scripts --- Makefile | 6 +++--- scripts/dev.sh | 15 +++++++++++++++ scripts/up.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100755 scripts/dev.sh create mode 100755 scripts/up.sh diff --git a/Makefile b/Makefile index 141b5acc..cb5178f8 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,8 @@ hooks: ## Install git hooks via lefthook # Core workflow ------------------------------------------------------------------------------ .PHONY: dev -dev: up deps-js build-js build-css watch ## Start local development (infra + asset deps + watch mode) +dev: ## Start local development (infra + asset deps + watch mode) + bash scripts/dev.sh "$(DCO_BIN)" .PHONY: dev-reset dev-reset: reset deps-js build-js build-css seed watch ## Full reset then start dev (destructive to local DB state) @@ -141,8 +142,7 @@ generate: ## Run code generation .PHONY: up up: ensure-compose ## Start Docker containers - $(DCO_BIN) up -d --remove-orphans - sleep 3 + bash scripts/up.sh "$(DCO_BIN)" .PHONY: down down: ensure-compose ## Stop Docker containers diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 00000000..83bdad49 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +"${SCRIPT_DIR}/up.sh" "${1:-}" + +npm install +npm run build +npx tailwindcss -i ./styles/styles.css -o ./static/styles_bundle.css + +echo "Tip: run 'nvm use v18.20.7' if JS tooling fails." +overmind start + diff --git a/scripts/up.sh b/scripts/up.sh new file mode 100755 index 00000000..ecbdb019 --- /dev/null +++ b/scripts/up.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + +resolve_compose() { + if [[ -n "${1:-}" ]]; then + # shellcheck disable=SC2206 + COMPOSE_CMD=($1) + return 0 + fi + + if command -v docker-compose >/dev/null 2>&1; then + COMPOSE_CMD=(docker-compose) + return 0 + fi + + if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then + COMPOSE_CMD=(docker compose) + return 0 + fi + + echo "No docker compose command found (docker-compose or docker compose)." >&2 + exit 1 +} + +resolve_compose "${1:-}" + +"${COMPOSE_CMD[@]}" up -d cache + +mailpit_err_file="$(mktemp)" +if ! "${COMPOSE_CMD[@]}" up -d mailpit >/dev/null 2>"${mailpit_err_file}"; then + if grep -Eiq "1025.*already allocated|Bind for .*:1025 failed|port is already allocated" "${mailpit_err_file}"; then + echo "Warning: Mailpit SMTP port 1025 is already in use; continuing without starting goship_mailpit." + echo "Set HOST_MAILPIT_SMTP_PORT to another port if you want goship_mailpit managed by this project." + else + echo "Failed to start mailpit:" >&2 + cat "${mailpit_err_file}" >&2 + rm -f "${mailpit_err_file}" + exit 1 + fi +fi + +rm -f "${mailpit_err_file}" +sleep 2 + From ba88de5efbeefdbde651e3d948c6b9a1b44dc5b8 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 14:10:21 -0800 Subject: [PATCH 008/291] refactor(test): split unit/integration workflow and add runtime-safe guards --- Makefile | 13 ++++++++---- README-framework-plan.md | 30 +++++++++++++++++++++++++-- pkg/controller/controller_test.go | 4 ++++ pkg/middleware/cache.go | 5 +++++ pkg/middleware/cache_test.go | 4 ++++ pkg/routing/routes/router.go | 4 ++-- pkg/services/cache.go | 3 +++ pkg/services/cache_test.go | 2 ++ pkg/services/tasks_test.go | 2 ++ scripts/precommit-tests.sh | 8 +------ scripts/test-integration.sh | 19 +++++++++++++++++ scripts/test-unit.sh | 19 +++++++++++++++++ scripts/test/integration-packages.txt | 9 ++++++++ scripts/test/unit-packages.txt | 11 ++++++++++ 14 files changed, 118 insertions(+), 15 deletions(-) create mode 100755 scripts/test-integration.sh create mode 100755 scripts/test-unit.sh create mode 100644 scripts/test/integration-packages.txt create mode 100644 scripts/test/unit-packages.txt diff --git a/Makefile b/Makefile index cb5178f8..f506fe0d 100644 --- a/Makefile +++ b/Makefile @@ -192,12 +192,17 @@ watch: ## Start all dev watchers/processes through overmind overmind start .PHONY: test -test: ## Run all tests - go test -p 1 ./... +test: ## Run Docker-free unit test package set + bash scripts/test-unit.sh + +.PHONY: test-integration +test-integration: ## Run integration test package set (may require Docker/infra) + bash scripts/test-integration.sh .PHONY: testall -testall: ## Run all tests with no caching - go test -count=1 -p 1 -count=1 ./... +testall: ## Run both unit and integration test package sets + bash scripts/test-unit.sh + bash scripts/test-integration.sh .PHONY: cover cover: ## Create a html coverage report and open it once generated diff --git a/README-framework-plan.md b/README-framework-plan.md index a7975f7f..381cb2d9 100644 --- a/README-framework-plan.md +++ b/README-framework-plan.md @@ -361,6 +361,26 @@ Decision: - Keep HTMX-first and no-build-friendly by default. - Ensure CLI removes manual compile-pipeline pain where Svelte is used. +## Unified Styling Policy + +Goal: + +- one unified styling system across GoShip modules and generated apps. + +Preferred direction: + +1. Use a single design system source of truth (tokens, component variants, spacing, colors, typography). +2. If Svelte is used, keep style parity with the same design tokens/components (e.g. shadcn + shadcn-svelte style equivalence). +3. Avoid fragmented ad-hoc component styling across stacks. + +LLM/agent styling guardrails: + +1. LLMs may change HTML/Templ/Svelte structure and behavior logic. +2. LLMs should not freely rewrite Tailwind styling. +3. Styling changes must be centralized in designated style system files with explicit documentation comments. +4. Generated/component docs should clearly mark style contract boundaries as "do not mutate directly" unless requested. +5. Framework prompts/checklists should enforce: "change behavior first, preserve style tokens/classes unless style task is explicitly requested." + ## Testing Strategy (Developer Ergonomics First) Test workflow should be fast and local-first without requiring Docker for most feedback loops. @@ -486,10 +506,16 @@ Test evidence: - `go test ./pkg/runtimeplan` 4. `R0.4` Testing harness improvements so default `make test` is Docker-free and fast. -Status: `in_progress` +Status: `completed` +Done when: +- default `make test` executes a Docker-free unit package set; +- integration/infra-heavy tests run via separate command; +- cache-dependent unit tests do not fail when cache service is disabled in runtime profile. +Test evidence: +- `bash scripts/test-unit.sh` 5. `R0.5` Establish package-level coverage baselines and close highest-value test gaps. -Status: `not_started` +Status: `in_progress` ### Phase 1: Core Abstractions diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index caa813b7..457ad751 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -149,6 +149,10 @@ func TestController_RenderPage(t *testing.T) { }) t.Run("page cache", func(t *testing.T) { + if c.Cache == nil { + t.Skip("cache service is not initialized in this runtime profile") + } + ctx, rec, ctr, p := setup() p.Cache.Enabled = true p.Cache.Tags = []string{"tag1"} diff --git a/pkg/middleware/cache.go b/pkg/middleware/cache.go index 57e0c959..55e1ae86 100644 --- a/pkg/middleware/cache.go +++ b/pkg/middleware/cache.go @@ -36,6 +36,11 @@ type CachedPage struct { func ServeCachedPage(ch *services.CacheClient) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { + // Cache backend is optional in some runtime profiles. + if ch == nil { + return next(c) + } + // Skip non GET requests if c.Request().Method != http.MethodGet { return next(c) diff --git a/pkg/middleware/cache_test.go b/pkg/middleware/cache_test.go index 8211a70a..9a7d639b 100644 --- a/pkg/middleware/cache_test.go +++ b/pkg/middleware/cache_test.go @@ -14,6 +14,10 @@ import ( ) func TestServeCachedPage(t *testing.T) { + if c.Cache == nil { + t.Skip("cache service is not initialized in this runtime profile") + } + // Cache a page cp := CachedPage{ URL: "/cache", diff --git a/pkg/routing/routes/router.go b/pkg/routing/routes/router.go index 44b73755..88b1ed87 100644 --- a/pkg/routing/routes/router.go +++ b/pkg/routing/routes/router.go @@ -200,8 +200,8 @@ func BuildRouter(c *services.Container) { ctr := controller.NewController(c) // Error handler - err := NewErrorHandler(ctr) - c.Web.HTTPErrorHandler = err.Get + errorHandler := NewErrorHandler(ctr) + c.Web.HTTPErrorHandler = errorHandler.Get generalRoutes(c, g, ctr) documentationRoutes(c, g, ctr) diff --git a/pkg/services/cache.go b/pkg/services/cache.go index e1f019ae..4eac4117 100644 --- a/pkg/services/cache.go +++ b/pkg/services/cache.go @@ -83,6 +83,9 @@ func NewCacheClient(cfg *config.Config) (*CacheClient, error) { // Close closes the connection to the cache func (c *CacheClient) Close() error { + if c == nil || c.Client == nil { + return nil + } return c.Client.Close() } diff --git a/pkg/services/cache_test.go b/pkg/services/cache_test.go index 29347634..cd2836d8 100644 --- a/pkg/services/cache_test.go +++ b/pkg/services/cache_test.go @@ -1,3 +1,5 @@ +//go:build integration + package services import ( diff --git a/pkg/services/tasks_test.go b/pkg/services/tasks_test.go index b76b843c..2897e2bd 100644 --- a/pkg/services/tasks_test.go +++ b/pkg/services/tasks_test.go @@ -1,3 +1,5 @@ +//go:build integration + package services import ( diff --git a/scripts/precommit-tests.sh b/scripts/precommit-tests.sh index c7cc1312..e8e3368f 100644 --- a/scripts/precommit-tests.sh +++ b/scripts/precommit-tests.sh @@ -3,12 +3,6 @@ set -euo pipefail echo "Running stateless pre-commit Go tests..." - -mkdir -p .cache/go-build -export GOCACHE="$(pwd)/.cache/go-build" - -go test ./config ./pkg/context ./pkg/funcmap ./pkg/htmx ./pkg/repos/msg -go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$' -go test ./pkg/runtimeplan +bash scripts/test-unit.sh echo "Pre-commit test suite passed." diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh new file mode 100755 index 00000000..63464d07 --- /dev/null +++ b/scripts/test-integration.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PKG_FILE="${ROOT_DIR}/scripts/test/integration-packages.txt" + +mkdir -p "${ROOT_DIR}/.cache/go-build" +export GOCACHE="${ROOT_DIR}/.cache/go-build" + +echo "Running integration test package set (may require Docker/infra)..." + +while IFS= read -r pkg; do + [[ -z "${pkg}" || "${pkg}" =~ ^# ]] && continue + go test -tags=integration "${pkg}" +done < "${PKG_FILE}" + +echo "Integration test package set passed." + diff --git a/scripts/test-unit.sh b/scripts/test-unit.sh new file mode 100755 index 00000000..3de40dbe --- /dev/null +++ b/scripts/test-unit.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PKG_FILE="${ROOT_DIR}/scripts/test/unit-packages.txt" + +mkdir -p "${ROOT_DIR}/.cache/go-build" +export GOCACHE="${ROOT_DIR}/.cache/go-build" + +echo "Running unit test package set (Docker-free)..." + +while IFS= read -r pkg; do + [[ -z "${pkg}" || "${pkg}" =~ ^# ]] && continue + go test "${pkg}" +done < "${PKG_FILE}" + +echo "Unit test package set passed." + diff --git a/scripts/test/integration-packages.txt b/scripts/test/integration-packages.txt new file mode 100644 index 00000000..2b697bb1 --- /dev/null +++ b/scripts/test/integration-packages.txt @@ -0,0 +1,9 @@ +# Packages with infrastructure-heavy tests +./pkg/routing/routes +./pkg/repos/emailsmanager +./pkg/repos/notifierrepo +./pkg/repos/profilerepo +./pkg/repos/subscriptions +./seeder +./pkg/services + diff --git a/scripts/test/unit-packages.txt b/scripts/test/unit-packages.txt new file mode 100644 index 00000000..1b3ab4db --- /dev/null +++ b/scripts/test/unit-packages.txt @@ -0,0 +1,11 @@ +# Docker-free unit package set +./config +./pkg/context +./pkg/controller +./pkg/funcmap +./pkg/htmx +./pkg/middleware +./pkg/repos/msg +./pkg/runtimeplan +./pkg/services + From 881de1f798790feb6a2abee3fcaa14ce8c57a3c6 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 15:14:15 -0800 Subject: [PATCH 009/291] refactor(app): move web routes and templ views under app/goship - relocate routes from pkg/routing/routes to app/goship/web/routes - relocate templates to app/goship/views and rewrite imports - update docs and Docker paths for new structure - improve dev flow with JS-free make dev and add dev-full - harden router/worker startup defaults and fix go module checksums --- Dockerfile | 6 +- Makefile | 8 +- Procfile.dev | 2 + README-framework-plan.md | 39 ++ README.md | 7 +- .../goship/views}/components/accordion.templ | 0 .../views}/components/accordion_templ.go | 6 +- .../goship/views}/components/auth.templ | 0 .../goship/views}/components/auth_templ.go | 8 +- .../goship/views}/components/bottom_nav.templ | 0 .../views}/components/bottom_nav_templ.go | 34 +- .../goship/views}/components/core.templ | 2 +- .../goship/views}/components/core_templ.go | 34 +- .../views}/components/documentation.templ | 0 .../views}/components/documentation_templ.go | 6 +- .../goship/views}/components/drawer.templ | 0 .../goship/views}/components/drawer_templ.go | 58 ++- .../views}/components/empty_page_msg.templ | 0 .../views}/components/empty_page_msg_templ.go | 6 +- .../goship/views}/components/forms.templ | 0 .../goship/views}/components/forms_templ.go | 6 +- .../goship/views}/components/heatmap.templ | 0 .../goship/views}/components/heatmap_templ.go | 2 +- .../goship/views}/components/icons.templ | 0 .../goship/views}/components/icons_templ.go | 40 +- .../goship/views}/components/loading.templ | 0 .../goship/views}/components/loading_templ.go | 2 +- .../goship/views}/components/logos.templ | 0 .../goship/views}/components/logos_templ.go | 72 ++- .../goship/views}/components/messages.templ | 0 .../views}/components/messages_templ.go | 12 +- .../goship/views}/components/navbar.templ | 0 .../goship/views}/components/navbar_templ.go | 84 ++-- .../goship/views}/components/payments.templ | 0 .../views}/components/payments_templ.go | 12 +- .../views}/components/permissions.templ | 0 .../views}/components/permissions_templ.go | 4 +- .../goship/views}/components/prev_nav.templ | 0 .../views}/components/prev_nav_templ.go | 8 +- .../goship/views}/components/profile.templ | 0 .../goship/views}/components/profile_templ.go | 54 ++- .../views}/components/pwa_install.templ | 0 .../views}/components/pwa_install_templ.go | 6 +- .../goship/views}/components/sidebar.templ | 0 .../goship/views}/components/sidebar_templ.go | 2 +- .../views}/components/theme_toggle.templ | 0 .../views}/components/theme_toggle_templ.go | 4 +- .../goship/views}/components/tooltip.templ | 0 .../goship/views}/components/tooltip_templ.go | 6 +- .../goship/views}/components/top_banner.templ | 0 .../views}/components/top_banner_templ.go | 2 +- .../goship/views}/emails/password_reset.templ | 0 .../views}/emails/password_reset_templ.go | 2 +- .../emails/registration_confirmation.templ | 0 .../emails/registration_confirmation_templ.go | 2 +- .../emails/subscription_confirmation.templ | 0 .../emails/subscription_confirmation_templ.go | 2 +- .../goship/views}/emails/test.templ | 0 .../goship/views}/emails/test_templ.go | 4 +- .../goship/views}/emails/update.templ | 0 .../goship/views}/emails/update_templ.go | 2 +- .../goship/views}/helpers/helpers.templ | 0 .../goship/views}/helpers/helpers_templ.go | 14 +- .../goship/views}/layouts/auth.templ | 2 +- .../goship/views}/layouts/auth_templ.go | 16 +- .../goship/views}/layouts/documentation.templ | 2 +- .../views}/layouts/documentation_templ.go | 4 +- .../goship/views}/layouts/email.templ | 0 .../goship/views}/layouts/email_templ.go | 2 +- .../goship/views}/layouts/landing_page.templ | 2 +- .../views}/layouts/landing_page_templ.go | 4 +- .../goship/views}/layouts/main.templ | 2 +- .../goship/views}/layouts/main_templ.go | 32 +- .../goship/views}/pages/about.templ | 2 +- .../goship/views}/pages/about_templ.go | 20 +- .../goship/views}/pages/contact.templ | 2 +- .../goship/views}/pages/contact_templ.go | 14 +- .../goship/views}/pages/delete_account.templ | 2 +- .../views}/pages/delete_account_templ.go | 16 +- .../views}/pages/docs_architecture.templ | 2 +- .../views}/pages/docs_architecture_templ.go | 4 +- .../goship/views}/pages/docs_emails.templ | 2 +- .../goship/views}/pages/docs_emails_templ.go | 4 +- .../goship/views}/pages/documentation.templ | 2 +- .../views}/pages/documentation_templ.go | 32 +- .../goship/views}/pages/email_subscribe.templ | 2 +- .../views}/pages/email_subscribe_templ.go | 10 +- .../goship/views}/pages/error.templ | 0 .../goship/views}/pages/error_templ.go | 36 +- .../goship/views}/pages/forgot_password.templ | 2 +- .../views}/pages/forgot_password_templ.go | 24 +- .../goship/views}/pages/healthcheck.templ | 0 .../goship/views}/pages/healthcheck_templ.go | 2 +- .../goship/views}/pages/home.templ | 2 +- .../goship/views}/pages/home_feed.templ | 2 +- .../goship/views}/pages/home_feed_templ.go | 62 +-- .../goship/views}/pages/home_templ.go | 16 +- .../goship/views}/pages/install_app.templ | 2 +- .../goship/views}/pages/install_app_templ.go | 60 +-- .../goship/views}/pages/invitations.templ | 0 .../goship/views}/pages/invitations_templ.go | 4 +- .../goship/views}/pages/landing_page.templ | 2 +- .../goship/views}/pages/landing_page_templ.go | 42 +- .../goship/views}/pages/login.templ | 2 +- .../goship/views}/pages/login_templ.go | 28 +- .../goship/views}/pages/notifications.templ | 2 +- .../views}/pages/notifications_templ.go | 34 +- .../goship/views}/pages/payments.templ | 2 +- .../goship/views}/pages/payments_templ.go | 30 +- .../goship/views}/pages/phone.templ | 2 +- .../goship/views}/pages/phone_templ.go | 18 +- .../goship/views}/pages/preferences.templ | 2 +- .../goship/views}/pages/preferences_templ.go | 44 +- .../goship/views}/pages/privacy_policy.templ | 2 +- .../views}/pages/privacy_policy_templ.go | 66 +-- .../goship/views}/pages/profile.templ | 2 +- .../goship/views}/pages/profile_templ.go | 4 +- .../goship/views}/pages/register.templ | 2 +- .../goship/views}/pages/register_templ.go | 38 +- .../goship/views}/pages/reset_password.templ | 2 +- .../views}/pages/reset_password_templ.go | 18 +- {templates => app/goship/views}/templates.go | 0 .../goship/web}/routes/about.go | 6 +- .../goship/web}/routes/about_test.go | 0 .../goship/web}/routes/clear_site_cookie.go | 0 .../goship/web}/routes/contact.go | 6 +- .../goship/web}/routes/delete_account.go | 6 +- .../routing => app/goship/web}/routes/docs.go | 6 +- .../goship/web}/routes/email_subscribe.go | 8 +- .../goship/web}/routes/error.go | 6 +- .../goship/web}/routes/forgot_password.go | 8 +- .../goship/web}/routes/healthcheck.go | 6 +- .../goship/web}/routes/helpers.go | 0 .../routing => app/goship/web}/routes/home.go | 6 +- .../goship/web}/routes/home_feed.go | 6 +- .../goship/web}/routes/install_app.go | 6 +- .../goship/web}/routes/landing.go | 6 +- .../goship/web}/routes/login.go | 6 +- .../goship/web}/routes/logout.go | 0 .../goship/web}/routes/notifications.go | 6 +- .../goship/web}/routes/payments.go | 6 +- .../goship/web}/routes/preferences.go | 6 +- .../goship/web}/routes/privacy.go | 6 +- .../goship/web}/routes/profile.go | 6 +- .../goship/web}/routes/profile_photo.go | 2 +- .../goship/web}/routes/push_notifs.go | 6 +- .../goship/web}/routes/realtime.go | 0 .../goship/web}/routes/register.go | 8 +- .../goship/web}/routes/register_test.go | 0 .../goship/web}/routes/reset_password.go | 6 +- .../goship/web}/routes/router.go | 459 +++++++++--------- .../goship/web}/routes/routes_test.go | 8 +- .../goship/web}/routes/upload_photo.go | 2 +- .../goship/web}/routes/verify_email.go | 0 .../web}/routes/verify_email_subscription.go | 2 +- cmd/web/main.go | 6 +- cmd/worker/main.go | 20 +- config/config.go | 9 +- config/config.yaml | 1 + docs/README.md | 5 +- docs/ai-agent-guide.md | 11 +- docs/architecture.md | 15 +- docs/http-routes.md | 2 +- docs/known-gaps-and-risks.md | 8 +- docs/project-scope-analysis.md | 9 +- docs/structure-and-boundaries.md | 49 ++ go.mod | 18 +- go.sum | 36 +- pkg/controller/controller_test.go | 6 +- pkg/controller/page.go | 2 +- pkg/htmx/htmx.go | 2 +- pkg/repos/emailsmanager/update_email.go | 4 +- pkg/routing/routenames/routenames.go | 15 +- pkg/runtimeplan/features.go | 1 - pkg/runtimeplan/features_test.go | 1 - pkg/runtimeplan/plan.go | 1 - pkg/runtimeplan/plan_test.go | 5 +- pkg/tasks/mail.go | 4 +- scripts/dev.sh | 10 +- scripts/test/integration-packages.txt | 2 +- static/meta_svelte_bundle.json | 2 +- 181 files changed, 1200 insertions(+), 941 deletions(-) create mode 100644 Procfile.dev rename {templates => app/goship/views}/components/accordion.templ (100%) rename {templates => app/goship/views}/components/accordion_templ.go (91%) rename {templates => app/goship/views}/components/auth.templ (100%) rename {templates => app/goship/views}/components/auth_templ.go (94%) rename {templates => app/goship/views}/components/bottom_nav.templ (100%) rename {templates => app/goship/views}/components/bottom_nav_templ.go (53%) rename {templates => app/goship/views}/components/core.templ (99%) rename {templates => app/goship/views}/components/core_templ.go (89%) rename {templates => app/goship/views}/components/documentation.templ (100%) rename {templates => app/goship/views}/components/documentation_templ.go (92%) rename {templates => app/goship/views}/components/drawer.templ (100%) rename {templates => app/goship/views}/components/drawer_templ.go (92%) rename {templates => app/goship/views}/components/empty_page_msg.templ (100%) rename {templates => app/goship/views}/components/empty_page_msg_templ.go (90%) rename {templates => app/goship/views}/components/forms.templ (100%) rename {templates => app/goship/views}/components/forms_templ.go (93%) rename {templates => app/goship/views}/components/heatmap.templ (100%) rename {templates => app/goship/views}/components/heatmap_templ.go (99%) rename {templates => app/goship/views}/components/icons.templ (100%) rename {templates => app/goship/views}/components/icons_templ.go (83%) rename {templates => app/goship/views}/components/loading.templ (100%) rename {templates => app/goship/views}/components/loading_templ.go (99%) rename {templates => app/goship/views}/components/logos.templ (100%) rename {templates => app/goship/views}/components/logos_templ.go (92%) rename {templates => app/goship/views}/components/messages.templ (100%) rename {templates => app/goship/views}/components/messages_templ.go (93%) rename {templates => app/goship/views}/components/navbar.templ (100%) rename {templates => app/goship/views}/components/navbar_templ.go (85%) rename {templates => app/goship/views}/components/payments.templ (100%) rename {templates => app/goship/views}/components/payments_templ.go (91%) rename {templates => app/goship/views}/components/permissions.templ (100%) rename {templates => app/goship/views}/components/permissions_templ.go (99%) rename {templates => app/goship/views}/components/prev_nav.templ (100%) rename {templates => app/goship/views}/components/prev_nav_templ.go (92%) rename {templates => app/goship/views}/components/profile.templ (100%) rename {templates => app/goship/views}/components/profile_templ.go (90%) rename {templates => app/goship/views}/components/pwa_install.templ (100%) rename {templates => app/goship/views}/components/pwa_install_templ.go (96%) rename {templates => app/goship/views}/components/sidebar.templ (100%) rename {templates => app/goship/views}/components/sidebar_templ.go (98%) rename {templates => app/goship/views}/components/theme_toggle.templ (100%) rename {templates => app/goship/views}/components/theme_toggle_templ.go (93%) rename {templates => app/goship/views}/components/tooltip.templ (100%) rename {templates => app/goship/views}/components/tooltip_templ.go (90%) rename {templates => app/goship/views}/components/top_banner.templ (100%) rename {templates => app/goship/views}/components/top_banner_templ.go (98%) rename {templates => app/goship/views}/emails/password_reset.templ (100%) rename {templates => app/goship/views}/emails/password_reset_templ.go (99%) rename {templates => app/goship/views}/emails/registration_confirmation.templ (100%) rename {templates => app/goship/views}/emails/registration_confirmation_templ.go (99%) rename {templates => app/goship/views}/emails/subscription_confirmation.templ (100%) rename {templates => app/goship/views}/emails/subscription_confirmation_templ.go (97%) rename {templates => app/goship/views}/emails/test.templ (100%) rename {templates => app/goship/views}/emails/test_templ.go (91%) rename {templates => app/goship/views}/emails/update.templ (100%) rename {templates => app/goship/views}/emails/update_templ.go (99%) rename {templates => app/goship/views}/helpers/helpers.templ (100%) rename {templates => app/goship/views}/helpers/helpers_templ.go (88%) rename {templates => app/goship/views}/layouts/auth.templ (96%) rename {templates => app/goship/views}/layouts/auth_templ.go (89%) rename {templates => app/goship/views}/layouts/documentation.templ (93%) rename {templates => app/goship/views}/layouts/documentation_templ.go (97%) rename {templates => app/goship/views}/layouts/email.templ (100%) rename {templates => app/goship/views}/layouts/email_templ.go (97%) rename {templates => app/goship/views}/layouts/landing_page.templ (93%) rename {templates => app/goship/views}/layouts/landing_page_templ.go (97%) rename {templates => app/goship/views}/layouts/main.templ (96%) rename {templates => app/goship/views}/layouts/main_templ.go (82%) rename {templates => app/goship/views}/pages/about.templ (95%) rename {templates => app/goship/views}/pages/about_templ.go (86%) rename {templates => app/goship/views}/pages/contact.templ (97%) rename {templates => app/goship/views}/pages/contact_templ.go (92%) rename {templates => app/goship/views}/pages/delete_account.templ (97%) rename {templates => app/goship/views}/pages/delete_account_templ.go (86%) rename {templates => app/goship/views}/pages/docs_architecture.templ (71%) rename {templates => app/goship/views}/pages/docs_architecture_templ.go (95%) rename {templates => app/goship/views}/pages/docs_emails.templ (70%) rename {templates => app/goship/views}/pages/docs_emails_templ.go (95%) rename {templates => app/goship/views}/pages/documentation.templ (99%) rename {templates => app/goship/views}/pages/documentation_templ.go (96%) rename {templates => app/goship/views}/pages/email_subscribe.templ (99%) rename {templates => app/goship/views}/pages/email_subscribe_templ.go (96%) rename {templates => app/goship/views}/pages/error.templ (100%) rename {templates => app/goship/views}/pages/error_templ.go (84%) rename {templates => app/goship/views}/pages/forgot_password.templ (96%) rename {templates => app/goship/views}/pages/forgot_password_templ.go (83%) rename {templates => app/goship/views}/pages/healthcheck.templ (100%) rename {templates => app/goship/views}/pages/healthcheck_templ.go (98%) rename {templates => app/goship/views}/pages/home.templ (97%) rename {templates => app/goship/views}/pages/home_feed.templ (99%) rename {templates => app/goship/views}/pages/home_feed_templ.go (81%) rename {templates => app/goship/views}/pages/home_templ.go (93%) rename {templates => app/goship/views}/pages/install_app.templ (99%) rename {templates => app/goship/views}/pages/install_app_templ.go (83%) rename {templates => app/goship/views}/pages/invitations.templ (100%) rename {templates => app/goship/views}/pages/invitations_templ.go (97%) rename {templates => app/goship/views}/pages/landing_page.templ (99%) rename {templates => app/goship/views}/pages/landing_page_templ.go (96%) rename {templates => app/goship/views}/pages/login.templ (98%) rename {templates => app/goship/views}/pages/login_templ.go (83%) rename {templates => app/goship/views}/pages/notifications.templ (99%) rename {templates => app/goship/views}/pages/notifications_templ.go (91%) rename {templates => app/goship/views}/pages/payments.templ (98%) rename {templates => app/goship/views}/pages/payments_templ.go (91%) rename {templates => app/goship/views}/pages/phone.templ (98%) rename {templates => app/goship/views}/pages/phone_templ.go (93%) rename {templates => app/goship/views}/pages/preferences.templ (99%) rename {templates => app/goship/views}/pages/preferences_templ.go (95%) rename {templates => app/goship/views}/pages/privacy_policy.templ (98%) rename {templates => app/goship/views}/pages/privacy_policy_templ.go (84%) rename {templates => app/goship/views}/pages/profile.templ (87%) rename {templates => app/goship/views}/pages/profile_templ.go (95%) rename {templates => app/goship/views}/pages/register.templ (98%) rename {templates => app/goship/views}/pages/register_templ.go (85%) rename {templates => app/goship/views}/pages/reset_password.templ (97%) rename {templates => app/goship/views}/pages/reset_password_templ.go (89%) rename {templates => app/goship/views}/templates.go (100%) rename {pkg/routing => app/goship/web}/routes/about.go (78%) rename {pkg/routing => app/goship/web}/routes/about_test.go (100%) rename {pkg/routing => app/goship/web}/routes/clear_site_cookie.go (100%) rename {pkg/routing => app/goship/web}/routes/contact.go (89%) rename {pkg/routing => app/goship/web}/routes/delete_account.go (93%) rename {pkg/routing => app/goship/web}/routes/docs.go (90%) rename {pkg/routing => app/goship/web}/routes/email_subscribe.go (94%) rename {pkg/routing => app/goship/web}/routes/error.go (91%) rename {pkg/routing => app/goship/web}/routes/forgot_password.go (93%) rename {pkg/routing => app/goship/web}/routes/healthcheck.go (75%) rename {pkg/routing => app/goship/web}/routes/helpers.go (100%) rename {pkg/routing => app/goship/web}/routes/home.go (87%) rename {pkg/routing => app/goship/web}/routes/home_feed.go (94%) rename {pkg/routing => app/goship/web}/routes/install_app.go (76%) rename {pkg/routing => app/goship/web}/routes/landing.go (96%) rename {pkg/routing => app/goship/web}/routes/login.go (95%) rename {pkg/routing => app/goship/web}/routes/logout.go (100%) rename {pkg/routing => app/goship/web}/routes/notifications.go (97%) rename {pkg/routing => app/goship/web}/routes/payments.go (98%) rename {pkg/routing => app/goship/web}/routes/preferences.go (98%) rename {pkg/routing => app/goship/web}/routes/privacy.go (80%) rename {pkg/routing => app/goship/web}/routes/profile.go (94%) rename {pkg/routing => app/goship/web}/routes/profile_photo.go (98%) rename {pkg/routing => app/goship/web}/routes/push_notifs.go (98%) rename {pkg/routing => app/goship/web}/routes/realtime.go (100%) rename {pkg/routing => app/goship/web}/routes/register.go (96%) rename {pkg/routing => app/goship/web}/routes/register_test.go (100%) rename {pkg/routing => app/goship/web}/routes/reset_password.go (92%) rename {pkg/routing => app/goship/web}/routes/router.go (52%) rename {pkg/routing => app/goship/web}/routes/routes_test.go (95%) rename {pkg/routing => app/goship/web}/routes/upload_photo.go (98%) rename {pkg/routing => app/goship/web}/routes/verify_email.go (100%) rename {pkg/routing => app/goship/web}/routes/verify_email_subscription.go (95%) create mode 100644 docs/structure-and-boundaries.md diff --git a/Dockerfile b/Dockerfile index 39ecc2f2..3a10e797 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,8 +48,8 @@ COPY --from=builder /app/goship-seed /goship-seed # Copy asynq tool COPY --from=builder /go/bin/asynq /usr/local/bin/ -# Copy the templates -COPY templates/ /app/templates/ +# Copy templ views +COPY app/goship/views/ /app/app/goship/views/ # Optional: Bind to a TCP port (document the ports the application listens on) EXPOSE 8000 @@ -70,4 +70,4 @@ COPY static /static ENTRYPOINT ["/entrypoint.sh"] # Clean up any unnecessary files -RUN apt-get purge -y gcc musl-dev libsqlite3-dev && apt-get autoremove -y && apt-get clean \ No newline at end of file +RUN apt-get purge -y gcc musl-dev libsqlite3-dev && apt-get autoremove -y && apt-get clean diff --git a/Makefile b/Makefile index f506fe0d..cdaaae01 100644 --- a/Makefile +++ b/Makefile @@ -44,9 +44,15 @@ hooks: ## Install git hooks via lefthook # Core workflow ------------------------------------------------------------------------------ .PHONY: dev -dev: ## Start local development (infra + asset deps + watch mode) +dev: ## Start local development (infra + Go processes; no JS/CSS build/watch) bash scripts/dev.sh "$(DCO_BIN)" +.PHONY: dev-full +dev-full: ## Start local development including JS/CSS watchers + bash scripts/up.sh "$(DCO_BIN)" + echo "Tip: run 'nvm use v18.20.7' if JS tooling fails." + overmind start + .PHONY: dev-reset dev-reset: reset deps-js build-js build-css seed watch ## Full reset then start dev (destructive to local DB state) diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 00000000..651d246e --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +watch-go: make watch-go +watch-go-worker: make worker diff --git a/README-framework-plan.md b/README-framework-plan.md index 381cb2d9..8ca5b51a 100644 --- a/README-framework-plan.md +++ b/README-framework-plan.md @@ -20,6 +20,16 @@ Build a highly productive, convention-first Go framework where developers can: 2. install batteries as versioned modules; 3. choose deployment/runtime mode without rewriting app code. +## Documentation Source Of Truth + +Implementation and architecture documentation lives in `docs/`. + +Primary files for ongoing refactor work: + +1. `docs/structure-and-boundaries.md` (canonical placement rules) +2. `docs/architecture.md` (runtime/system behavior) +3. `docs/ai-agent-guide.md` (agent execution conventions) + Primary framing: - GoShip aims to be a Ruby on Rails alternative in Go, with comparable batteries-included productivity and developer ergonomics. @@ -84,6 +94,7 @@ CLI responsibilities: 2. `goship profile set ` updates environment/process presets. 3. `goship module add ` updates module manifest + initializer wiring. 4. `goship jobs backend set ` updates adapter config with capability checks. +5. `goship new` should auto-install templ tooling and keep it current in generated projects (including `go get -u github.com/a-h/templ` as part of bootstrap/update workflow). ## Core Product Goals @@ -93,6 +104,14 @@ CLI responsibilities: 4. Modular infrastructure adapters. 5. Strong defaults with optional escape hatches. +## Current Repository Shape (Post-Refactor) + +1. `app/goship/` contains app-specific web handlers and templ views. +2. `pkg/` currently remains the framework/infrastructure layer. +3. `cmd/` contains process entrypoints. + +Note: `pkg/repos` and `pkg/services` are intentionally still centralized for now and will be split into app-specific vs reusable framework modules in a dedicated follow-up pass. + ## Architecture Style (Pragmatic) GoShip will use a pragmatic blend: @@ -260,6 +279,26 @@ Policy: 2. Backend selection happens in config + runtime wiring. 3. Application/business code must not depend directly on concrete infra clients. +## Routing Organization (Rails-Inspired, Pragmatic) + +Target: + +1. Keep one orchestration router entrypoint. +2. Register routes by domain slices (auth, public, docs, billing, notifications, etc.). +3. Keep domain registration functions small and convention-driven. +4. Avoid over-engineered plugin systems in early stages. + +Current direction: + +1. `BuildRouter` handles shared middleware and runtime feature gating. +2. Domain registration is split into focused files: +- public +- docs +- auth +- external +- realtime +3. App composition happens via a single route composition function, preparing for multi-app mounting later. + ## Required Core Interfaces Create stable contracts in `core` so app code is backend-agnostic: diff --git a/README.md b/README.md index 8ebf544b..6ef52fee 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ For in-depth info on the architecture of the project, please see the [mikestefan The most important aspects to note are: - The `Container` struct is instantiated when the app starts up and is used to pass dependencies around the app, specifically core services like `Logger`, `Database`, `ORM`, `Cache`, etc. -- Routes are defined in `routes/routes.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `templates/pages/` that represents the view. +- Routes are defined in `app/goship/web/routes/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/pages/` that represents the view. ## Database @@ -515,13 +515,13 @@ g.DELETE("/posts/:id", postRoute.Destroy).Name = "posts.destroy" ##### Generated views ```go -// templates/posts.templ +// app/goship/views/posts.templ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" ) templ PostsIndex(page *controller.Page) { @@ -557,4 +557,3 @@ type Post struct { } ``` - diff --git a/templates/components/accordion.templ b/app/goship/views/components/accordion.templ similarity index 100% rename from templates/components/accordion.templ rename to app/goship/views/components/accordion.templ diff --git a/templates/components/accordion_templ.go b/app/goship/views/components/accordion_templ.go similarity index 91% rename from templates/components/accordion_templ.go rename to app/goship/views/components/accordion_templ.go index 251ec574..5e981543 100644 --- a/templates/components/accordion_templ.go +++ b/app/goship/views/components/accordion_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -38,7 +38,7 @@ func AccordionItem(title string, expanded bool) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("{ expanded: %v }", expanded)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/accordion.templ`, Line: 8, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/accordion.templ`, Line: 8, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -51,7 +51,7 @@ func AccordionItem(title string, expanded bool) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/accordion.templ`, Line: 14, Col: 10} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/accordion.templ`, Line: 14, Col: 10} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/templates/components/auth.templ b/app/goship/views/components/auth.templ similarity index 100% rename from templates/components/auth.templ rename to app/goship/views/components/auth.templ diff --git a/templates/components/auth_templ.go b/app/goship/views/components/auth_templ.go similarity index 94% rename from templates/components/auth_templ.go rename to app/goship/views/components/auth_templ.go index 545985d7..a802c451 100644 --- a/templates/components/auth_templ.go +++ b/app/goship/views/components/auth_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameLogin)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/auth.templ`, Line: 23, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 23, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -65,7 +65,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRegister)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/auth.templ`, Line: 40, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 40, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -84,7 +84,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameForgotPassword)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/auth.templ`, Line: 61, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 61, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/templates/components/bottom_nav.templ b/app/goship/views/components/bottom_nav.templ similarity index 100% rename from templates/components/bottom_nav.templ rename to app/goship/views/components/bottom_nav.templ diff --git a/templates/components/bottom_nav_templ.go b/app/goship/views/components/bottom_nav_templ.go similarity index 53% rename from templates/components/bottom_nav_templ.go rename to app/goship/views/components/bottom_nav_templ.go index 3c7d9ae1..7124fcfb 100644 --- a/templates/components/bottom_nav_templ.go +++ b/app/goship/views/components/bottom_nav_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -39,7 +39,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(page.ShowBottomNavbar)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/bottom_nav.templ`, Line: 9, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 9, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -57,7 +57,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/bottom_nav.templ`, Line: 12, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 12, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -84,7 +84,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/bottom_nav.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -95,25 +95,25 @@ func BottomNav(page *controller.Page) templ.Component { return templ_7745c5c3_Err } if page.SelectedBottomNavbarItem.Value == "home" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " x-data=\"{ selected: 'home' }\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " x-data=\"{ selected: 'home' }\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if page.SelectedBottomNavbarItem.Value == "notifications" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " x-data=\"{ selected: 'notifications' }\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " x-data=\"{ selected: 'notifications' }\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if page.SelectedBottomNavbarItem.Value == "settings" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " x-data=\"{ selected: 'settings' }\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " x-data=\"{ selected: 'settings' }\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if page.SelectedBottomNavbarItem.Value == "profile" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " x-data=\"{ selected: 'profile' }\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " x-data=\"{ selected: 'profile' }\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -125,33 +125,33 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/bottom_nav.templ`, Line: 39, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 39, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" type=\"button\" class=\"inline-flex flex-col items-center justify-center px-5 hover:bg-gray-100 dark:hover:bg-gray-800 group rounded-xl m-2\" @click=\"selected = 'home';\"> ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" type=\"button\" class=\"inline-flex flex-col items-center justify-center px-5 hover:bg-gray-100 dark:hover:bg-gray-800 group rounded-xl m-2\" @click=\"selected = 'profile';\"> ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/components/core.templ b/app/goship/views/components/core.templ similarity index 99% rename from templates/components/core.templ rename to app/goship/views/components/core.templ index 25b67773..1773ae18 100644 --- a/templates/components/core.templ +++ b/app/goship/views/components/core.templ @@ -4,7 +4,7 @@ import ( "fmt" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" - "github.com/mikestefanello/pagoda/templates/helpers" + "github.com/mikestefanello/pagoda/app/goship/views/helpers" "strings" ) diff --git a/templates/components/core_templ.go b/app/goship/views/components/core_templ.go similarity index 89% rename from templates/components/core_templ.go rename to app/goship/views/components/core_templ.go index b3f08c2d..3f8c8391 100644 --- a/templates/components/core_templ.go +++ b/app/goship/views/components/core_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,9 +10,9 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" + "github.com/mikestefanello/pagoda/app/goship/views/helpers" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" - "github.com/mikestefanello/pagoda/templates/helpers" "strings" ) @@ -44,7 +44,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 13, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 13, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -58,7 +58,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("| %s", page.Title)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 15, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 15, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -72,7 +72,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("favicon.png")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 18, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 18, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -90,7 +90,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(page.Metatags.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 23, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 23, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(page.Metatags.Keywords, ", ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 26, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 26, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -156,7 +156,7 @@ func CSS() templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("svelte_bundle.css")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 40, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 40, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -169,7 +169,7 @@ func CSS() templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("styles_bundle.css")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 41, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 41, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -182,13 +182,13 @@ func CSS() templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("manifest.json")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 42, Col: 88} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 42, Col: 88} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -224,7 +224,7 @@ func JS() templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(helpers.File("icon.png"))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 74, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 74, Col: 94} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func JS() templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("main.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 83, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 83, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -250,7 +250,7 @@ func JS() templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("vanilla_bundle.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 84, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 84, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -263,7 +263,7 @@ func JS() templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.ServiceWorkerFile("service-worker.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 87, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 87, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -482,7 +482,7 @@ func TextFooter(page *controller.Page) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameAboutUs)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 217, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 217, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -495,7 +495,7 @@ func TextFooter(page *controller.Page) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNamePrivacyPolicy)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/core.templ`, Line: 225, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 225, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { diff --git a/templates/components/documentation.templ b/app/goship/views/components/documentation.templ similarity index 100% rename from templates/components/documentation.templ rename to app/goship/views/components/documentation.templ diff --git a/templates/components/documentation_templ.go b/app/goship/views/components/documentation_templ.go similarity index 92% rename from templates/components/documentation_templ.go rename to app/goship/views/components/documentation_templ.go index e406da74..1e7b25fc 100644 --- a/templates/components/documentation_templ.go +++ b/app/goship/views/components/documentation_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -36,7 +36,7 @@ func SectionTitle(title string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/documentation.templ`, Line: 4, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/documentation.templ`, Line: 4, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -78,7 +78,7 @@ func SubSectionTitle(title string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/documentation.templ`, Line: 8, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/documentation.templ`, Line: 8, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/templates/components/drawer.templ b/app/goship/views/components/drawer.templ similarity index 100% rename from templates/components/drawer.templ rename to app/goship/views/components/drawer.templ diff --git a/templates/components/drawer_templ.go b/app/goship/views/components/drawer_templ.go similarity index 92% rename from templates/components/drawer_templ.go rename to app/goship/views/components/drawer_templ.go index 012e7899..4a7bf010 100644 --- a/templates/components/drawer_templ.go +++ b/app/goship/views/components/drawer_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -34,7 +34,7 @@ func Drawer(page *controller.Page, showTopBar bool) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
= 1024,\n\t\t\tconditionalToggle() {\n\t\t\t\tif (window.innerWidth < 1024) {\n\t\t\t\t\tthis.isOpen = false;\n\t\t\t\t}\n\t\t\t},\n\t\t\thandleEscape() {\n\t\t\t\tif (window.innerWidth < 1024) {\n\t\t\t\t\tthis.isOpen = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\" @keydown.escape.window=\"handleEscape()\" x-cloak @resize.window=\"isOpen = window.innerWidth >= 1024\" class=\"w-full\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -46,7 +46,7 @@ func Drawer(page *controller.Page, showTopBar bool) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 29, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 29, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func drawerButtonToggle(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 68, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 68, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -154,7 +154,7 @@ func drawerPanel(page *controller.Page, showTopBar bool) templ.Component { templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -413,8 +413,12 @@ func drawerTopBar(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameHomeFeed)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var11))) + var templ_7745c5c3_Var11 templ.SafeURL + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameHomeFeed))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 147, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -427,8 +431,12 @@ func drawerTopBar(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameLandingPage)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) + var templ_7745c5c3_Var12 templ.SafeURL + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 149, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -444,7 +452,7 @@ func drawerTopBar(page *controller.Page) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(page.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 161, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 161, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -488,7 +496,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUserProfilePicURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 182, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 182, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -506,7 +514,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 185, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 185, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -519,7 +527,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 186, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 186, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -555,7 +563,7 @@ func overlay() templ.Component { templ_7745c5c3_Var18 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -591,7 +599,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 202, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 202, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -604,7 +612,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotifications")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 214, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 214, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -617,7 +625,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/components/drawer.templ`, Line: 224, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 224, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -662,7 +670,7 @@ func dropdownEntry(icon templ.Component, menuTitle string) templ.Component { templ_7745c5c3_Var23 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" _=\"on click\n\t\t\t\tcall event.preventDefault()\n\t\t\t\tcall event.stopPropagation()\n\t\t\t\tif not @disabled\n\t\t\t\tthen call Swal.fire({\n\t\t\t\t\ttitle: 'Confirm',\n\t\t\t\t\ttext: 'Are you sure you want to delete your account and data?',\n\t\t\t\t\ticon: 'warning',\n\t\t\t\t\tshowCancelButton: true,\n\t\t\t\t\tcancelButtonText: 'Cancel',\n\t\t\t\t\tconfirmButtonText: 'Yes, delete it!'\n\t\t\t\t})\n\t\t\t\tthen if result.isConfirmed document.querySelector('#deleteAccountLink').click()\">Delete account and data right now
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/pages/docs_architecture.templ b/app/goship/views/pages/docs_architecture.templ similarity index 71% rename from templates/pages/docs_architecture.templ rename to app/goship/views/pages/docs_architecture.templ index 33fc6135..ca4f7136 100644 --- a/templates/pages/docs_architecture.templ +++ b/app/goship/views/pages/docs_architecture.templ @@ -2,7 +2,7 @@ package pages import "github.com/mikestefanello/pagoda/pkg/controller" -// "github.com/mikestefanello/pagoda/templates/components" +// "github.com/mikestefanello/pagoda/app/goship/views/components" templ DocumentationArchitecturePage(page *controller.Page) { @docsPageLayout(page) { } diff --git a/templates/pages/docs_architecture_templ.go b/app/goship/views/pages/docs_architecture_templ.go similarity index 95% rename from templates/pages/docs_architecture_templ.go rename to app/goship/views/pages/docs_architecture_templ.go index ffee2b1f..ffaaf952 100644 --- a/templates/pages/docs_architecture_templ.go +++ b/app/goship/views/pages/docs_architecture_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import "github.com/mikestefanello/pagoda/pkg/controller" -// "github.com/mikestefanello/pagoda/templates/components" +// "github.com/mikestefanello/pagoda/app/goship/views/components" func DocumentationArchitecturePage(page *controller.Page) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context diff --git a/templates/pages/docs_emails.templ b/app/goship/views/pages/docs_emails.templ similarity index 70% rename from templates/pages/docs_emails.templ rename to app/goship/views/pages/docs_emails.templ index ba1ff9b2..7ca2812d 100644 --- a/templates/pages/docs_emails.templ +++ b/app/goship/views/pages/docs_emails.templ @@ -2,7 +2,7 @@ package pages import "github.com/mikestefanello/pagoda/pkg/controller" -// "github.com/mikestefanello/pagoda/templates/components" +// "github.com/mikestefanello/pagoda/app/goship/views/components" templ DocumentationEmailingPage(page *controller.Page) { @docsPageLayout(page) { } diff --git a/templates/pages/docs_emails_templ.go b/app/goship/views/pages/docs_emails_templ.go similarity index 95% rename from templates/pages/docs_emails_templ.go rename to app/goship/views/pages/docs_emails_templ.go index d7755f90..2530e809 100644 --- a/templates/pages/docs_emails_templ.go +++ b/app/goship/views/pages/docs_emails_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import "github.com/mikestefanello/pagoda/pkg/controller" -// "github.com/mikestefanello/pagoda/templates/components" +// "github.com/mikestefanello/pagoda/app/goship/views/components" func DocumentationEmailingPage(page *controller.Page) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context diff --git a/templates/pages/documentation.templ b/app/goship/views/pages/documentation.templ similarity index 99% rename from templates/pages/documentation.templ rename to app/goship/views/pages/documentation.templ index feb4f143..1e791238 100644 --- a/templates/pages/documentation.templ +++ b/app/goship/views/pages/documentation.templ @@ -3,7 +3,7 @@ package pages import ( "fmt" "github.com/mikestefanello/pagoda/pkg/controller" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" ) templ DocumentationLandingPage(page *controller.Page) { diff --git a/templates/pages/documentation_templ.go b/app/goship/views/pages/documentation_templ.go similarity index 96% rename from templates/pages/documentation_templ.go rename to app/goship/views/pages/documentation_templ.go index cd3eb438..346b8d66 100644 --- a/templates/pages/documentation_templ.go +++ b/app/goship/views/pages/documentation_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,8 +10,8 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" - "github.com/mikestefanello/pagoda/templates/components" ) func DocumentationLandingPage(page *controller.Page) templ.Component { @@ -106,7 +106,7 @@ func docsPageLayout(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 44, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 44, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -497,7 +497,7 @@ func iconWithTitle(icon templ.Component, title string) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 106, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 106, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -916,7 +916,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("controlsAccordionItem%s", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 208, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 208, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -929,7 +929,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("accordionItem%s", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 211, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 211, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -942,7 +942,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("selectedAccordionItem = '%s'", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 212, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 212, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -955,7 +955,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("selectedAccordionItem === '%s' ? 'text-onSurfaceStrong dark:text-onSurfaceDarkStrong font-bold' : 'text-onSurface dark:text-onSurfaceDark font-medium'", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 213, Col: 189} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 213, Col: 189} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { @@ -968,7 +968,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("selectedAccordionItem === '%s' ? 'true' : 'false'", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 214, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 214, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { @@ -981,7 +981,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 216, Col: 10} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 216, Col: 10} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { @@ -994,7 +994,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("selectedAccordionItem === '%s' ? 'rotate-180' : ''", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 217, Col: 257} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 217, Col: 257} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { @@ -1007,7 +1007,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var35 string templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("selectedAccordionItem === '%s'", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 221, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 221, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { @@ -1020,7 +1020,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("accordionItem%s", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 221, Col: 129} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 221, Col: 129} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { @@ -1033,7 +1033,7 @@ func singleOpenAccordionEntry(entryName, title string, content templ.Component) var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("controlsAccordionItem%s", entryName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 221, Col: 213} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 221, Col: 213} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { @@ -1076,7 +1076,7 @@ func singleOpenAccordion() templ.Component { templ_7745c5c3_Var38 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1128,7 +1128,7 @@ func listEntry(icon templ.Component, sentence string) templ.Component { var templ_7745c5c3_Var40 string templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(sentence) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/documentation.templ`, Line: 239, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/documentation.templ`, Line: 239, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/email_subscribe.templ b/app/goship/views/pages/email_subscribe.templ similarity index 99% rename from templates/pages/email_subscribe.templ rename to app/goship/views/pages/email_subscribe.templ index 58e89cd6..612abed2 100644 --- a/templates/pages/email_subscribe.templ +++ b/app/goship/views/pages/email_subscribe.templ @@ -3,7 +3,7 @@ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" ) templ EmailSubscribe(page *controller.Page) { diff --git a/templates/pages/email_subscribe_templ.go b/app/goship/views/pages/email_subscribe_templ.go similarity index 96% rename from templates/pages/email_subscribe_templ.go rename to app/goship/views/pages/email_subscribe_templ.go index 9ecc039f..205ad8fd 100644 --- a/templates/pages/email_subscribe_templ.go +++ b/app/goship/views/pages/email_subscribe_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -9,9 +9,9 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" ) func EmailSubscribe(page *controller.Page) templ.Component { @@ -94,7 +94,7 @@ func subscribeForm(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("emailSubscribe.post")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/email_subscribe.templ`, Line: 90, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/email_subscribe.templ`, Line: 90, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -116,7 +116,7 @@ func subscribeForm(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/email_subscribe.templ`, Line: 97, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/email_subscribe.templ`, Line: 97, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -129,7 +129,7 @@ func subscribeForm(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/email_subscribe.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/email_subscribe.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/error.templ b/app/goship/views/pages/error.templ similarity index 100% rename from templates/pages/error.templ rename to app/goship/views/pages/error.templ diff --git a/templates/pages/error_templ.go b/app/goship/views/pages/error_templ.go similarity index 84% rename from templates/pages/error_templ.go rename to app/goship/views/pages/error_templ.go index 2e8c86c0..4d12af6f 100644 --- a/templates/pages/error_templ.go +++ b/app/goship/views/pages/error_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -42,7 +42,7 @@ func Error(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", page.StatusCode)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 14, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 14, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -73,8 +73,12 @@ func Error(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameHomeFeed)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3))) + var templ_7745c5c3_Var3 templ.SafeURL + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameHomeFeed))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 24, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -87,8 +91,12 @@ func Error(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameLandingPage)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) + var templ_7745c5c3_Var4 templ.SafeURL + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 26, Col: 67} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -130,7 +138,7 @@ func title(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("Please try again.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 38, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 38, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -140,7 +148,7 @@ func title(page *controller.Page) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs("You are not authorized to view the requested page.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 40, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 40, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -150,7 +158,7 @@ func title(page *controller.Page) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("Something's missing.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 42, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 42, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -160,7 +168,7 @@ func title(page *controller.Page) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("Something went wrong") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 44, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 44, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -196,7 +204,7 @@ func subtitle(page *controller.Page) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("Oops! Something went wrong on our end. Please refresh the page or try again later.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 50, Col: 88} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 50, Col: 88} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -206,7 +214,7 @@ func subtitle(page *controller.Page) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs("You are not authorized to view the requested page.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 52, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 52, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -216,7 +224,7 @@ func subtitle(page *controller.Page) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs("Sorry, we can't find that page. You'll find lots to explore on the home page. ") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 54, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 54, Col: 84} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -226,7 +234,7 @@ func subtitle(page *controller.Page) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs("Something went wrong. Please refresh the page or try again later.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/error.templ`, Line: 56, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/error.templ`, Line: 56, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/forgot_password.templ b/app/goship/views/pages/forgot_password.templ similarity index 96% rename from templates/pages/forgot_password.templ rename to app/goship/views/pages/forgot_password.templ index 2c1f34f0..84e87028 100644 --- a/templates/pages/forgot_password.templ +++ b/app/goship/views/pages/forgot_password.templ @@ -3,7 +3,7 @@ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/routing/routenames" ) diff --git a/templates/pages/forgot_password_templ.go b/app/goship/views/pages/forgot_password_templ.go similarity index 83% rename from templates/pages/forgot_password_templ.go rename to app/goship/views/pages/forgot_password_templ.go index 049e07b4..73ecc500 100644 --- a/templates/pages/forgot_password_templ.go +++ b/app/goship/views/pages/forgot_password_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -9,10 +9,10 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" ) func ForgotPassword(page *controller.Page) templ.Component { @@ -41,8 +41,12 @@ func ForgotPassword(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(page.ToURL("forgot_password.post")) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2))) + var templ_7745c5c3_Var2 templ.SafeURL + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL("forgot_password.post"))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/forgot_password.templ`, Line: 14, Col: 57} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -62,7 +66,7 @@ func ForgotPassword(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/forgot_password.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/forgot_password.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -75,7 +79,7 @@ func ForgotPassword(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/forgot_password.templ`, Line: 31, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/forgot_password.templ`, Line: 31, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -93,8 +97,12 @@ func ForgotPassword(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameLandingPage)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6))) + var templ_7745c5c3_Var6 templ.SafeURL + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/forgot_password.templ`, Line: 42, Col: 66} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/pages/healthcheck.templ b/app/goship/views/pages/healthcheck.templ similarity index 100% rename from templates/pages/healthcheck.templ rename to app/goship/views/pages/healthcheck.templ diff --git a/templates/pages/healthcheck_templ.go b/app/goship/views/pages/healthcheck_templ.go similarity index 98% rename from templates/pages/healthcheck_templ.go rename to app/goship/views/pages/healthcheck_templ.go index cdf9df6a..b947f410 100644 --- a/templates/pages/healthcheck_templ.go +++ b/app/goship/views/pages/healthcheck_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/templates/pages/home.templ b/app/goship/views/pages/home.templ similarity index 97% rename from templates/pages/home.templ rename to app/goship/views/pages/home.templ index a6ae823c..ccae0e69 100644 --- a/templates/pages/home.templ +++ b/app/goship/views/pages/home.templ @@ -5,7 +5,7 @@ import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/helpers" + "github.com/mikestefanello/pagoda/app/goship/views/helpers" ) templ Home(page *controller.Page) { diff --git a/templates/pages/home_feed.templ b/app/goship/views/pages/home_feed.templ similarity index 99% rename from templates/pages/home_feed.templ rename to app/goship/views/pages/home_feed.templ index cf5caaec..c3f3919b 100644 --- a/templates/pages/home_feed.templ +++ b/app/goship/views/pages/home_feed.templ @@ -5,7 +5,7 @@ import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" "strconv" ) diff --git a/templates/pages/home_feed_templ.go b/app/goship/views/pages/home_feed_templ.go similarity index 81% rename from templates/pages/home_feed_templ.go rename to app/goship/views/pages/home_feed_templ.go index d15414b5..e61cd027 100644 --- a/templates/pages/home_feed_templ.go +++ b/app/goship/views/pages/home_feed_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,10 +10,10 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" "strconv" ) @@ -45,7 +45,7 @@ func HomeFeed(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 16, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 16, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -67,7 +67,7 @@ func HomeFeed(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameGetHomeFeedButtons)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 21, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 21, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -147,7 +147,7 @@ func hello(page *controller.Page, justFinishedOnboarded bool) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 51, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 51, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -165,7 +165,7 @@ func hello(page *controller.Page, justFinishedOnboarded bool) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(justFinishedOnboarded)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 59, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 59, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -295,7 +295,7 @@ func homeFeedButtonsWithCounts(page *controller.Page, numDrafts, numLikedQuestio var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameGetHomeFeedButtons)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 136, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 136, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -308,20 +308,20 @@ func homeFeedButtonsWithCounts(page *controller.Page, numDrafts, numLikedQuestio var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/home_feed.templ`, Line: 145, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/home_feed.templ`, Line: 145, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" hx-target=\"#homeFeedItems\" hx-select=\"#homeFeedItems\" hx-indicator=\"next #local-page-loading\" hx-swap=\"innerHTML\" class=\"bg-gray-400 dark:bg-gray-500 hover:bg-gray-400 dark:hover:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-100 font-bold py-2 px-4 rounded-full inline-flex items-center m-2\" _=\"on click\n remove .bg-gray-400 from <button/> in #homeFeedButtonGroup\n\t\t\t\tthen remove .dark:bg-gray-500 from <button/> in #homeFeedButtonGroup\n\t\t\t\tthen add .bg-gray-300 to <button/> in #homeFeedButtonGroup\n\t\t\t\tthen add .dark:bg-gray-800 to <button/> in #homeFeedButtonGroup\n\t\t\t\tthen remove .bg-gray-300 from me\n\t\t\t\tthen remove .dark:bg-gray-800 from me\n then add .bg-gray-400 to me\n\t\t\t\tthen add .dark:bg-gray-500 to me\n \"> ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" hx-target=\"#homeFeedItems\" hx-select=\"#homeFeedItems\" hx-indicator=\"next #local-page-loading\" hx-swap=\"innerHTML\" class=\"bg-gray-400 dark:bg-gray-500 hover:bg-gray-400 dark:hover:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-100 font-bold py-2 px-4 rounded-full inline-flex items-center m-2\" _=\"on click\n remove .bg-gray-400 from
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -347,7 +347,7 @@ func installPWA() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -395,7 +395,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs("Open Safari on your device and navigate to the Goship website:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 136, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 136, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -416,7 +416,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs("Open the share menu:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 138, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 138, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -437,7 +437,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs("Select \"Add to home Screen\":") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 140, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 140, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -458,7 +458,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs("Confirm by tapping \"Add\":") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 142, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 142, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -479,7 +479,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs("App will be installed to home screen:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 144, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 144, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -500,7 +500,7 @@ func iPhone() templ.Component { var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs("Open the app, enjoy!") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 146, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 146, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -546,7 +546,7 @@ func android() templ.Component { var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs("Open Chrome on your device and navigate to the Goship website:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 153, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 153, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -567,7 +567,7 @@ func android() templ.Component { var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs("Open the 3-dot options menu:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 155, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 155, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -588,7 +588,7 @@ func android() templ.Component { var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs("Select \"Install app\" (or \"Add to home Screen\" on older devices):") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 157, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 157, Col: 78} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -609,7 +609,7 @@ func android() templ.Component { var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs("Confirm by tapping \"Install\" (or \"Add to home screen \" on older devices):") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 159, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 159, Col: 87} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -634,7 +634,7 @@ func android() templ.Component { var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs("App will be installed to home screen:") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 162, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 162, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { @@ -655,7 +655,7 @@ func android() templ.Component { var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs("Open the app, enjoy!") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 164, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 164, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { @@ -697,7 +697,7 @@ func warningDontDoTheseStepsJustYet() templ.Component { var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs("Don't do these steps just yet,") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 175, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 175, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { @@ -710,7 +710,7 @@ func warningDontDoTheseStepsJustYet() templ.Component { var templ_7745c5c3_Var35 string templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs("you will need to do them from the correct browser, see below steps.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 175, Col: 144} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 175, Col: 144} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { @@ -752,7 +752,7 @@ func screen(url, alt string) templ.Component { var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(url) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 184, Col: 12} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 184, Col: 12} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { @@ -765,7 +765,7 @@ func screen(url, alt string) templ.Component { var templ_7745c5c3_Var38 string templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(alt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/install_app.templ`, Line: 185, Col: 12} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/install_app.templ`, Line: 185, Col: 12} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/invitations.templ b/app/goship/views/pages/invitations.templ similarity index 100% rename from templates/pages/invitations.templ rename to app/goship/views/pages/invitations.templ diff --git a/templates/pages/invitations_templ.go b/app/goship/views/pages/invitations_templ.go similarity index 97% rename from templates/pages/invitations_templ.go rename to app/goship/views/pages/invitations_templ.go index 1e00aa41..7a433e2c 100644 --- a/templates/pages/invitations_templ.go +++ b/app/goship/views/pages/invitations_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -41,7 +41,7 @@ func InvitationsComponent(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("invitations")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/invitations.templ`, Line: 15, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/invitations.templ`, Line: 15, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/landing_page.templ b/app/goship/views/pages/landing_page.templ similarity index 99% rename from templates/pages/landing_page.templ rename to app/goship/views/pages/landing_page.templ index e7339885..68dbcad0 100644 --- a/templates/pages/landing_page.templ +++ b/app/goship/views/pages/landing_page.templ @@ -4,7 +4,7 @@ import ( "fmt" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/routing/routenames" ) diff --git a/templates/pages/landing_page_templ.go b/app/goship/views/pages/landing_page_templ.go similarity index 96% rename from templates/pages/landing_page_templ.go rename to app/goship/views/pages/landing_page_templ.go index 95ecb128..a453fd50 100644 --- a/templates/pages/landing_page_templ.go +++ b/app/goship/views/pages/landing_page_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,10 +10,10 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" ) func LandingPage(page *controller.Page) templ.Component { @@ -306,7 +306,7 @@ func welcomeScreen(page *controller.Page, d types.LandingPage) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(d.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 92, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 92, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -319,7 +319,7 @@ func welcomeScreen(page *controller.Page, d types.LandingPage) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(d.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 100, Col: 13} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 100, Col: 13} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -332,7 +332,7 @@ func welcomeScreen(page *controller.Page, d types.LandingPage) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(d.Subtitle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 103, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 103, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -468,8 +468,12 @@ func getStartedButton(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 templ.SafeURL = templ.URL(page.ToURL(routenames.RouteNameDocs)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var11))) + var templ_7745c5c3_Var11 templ.SafeURL + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameDocs))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 169, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -609,7 +613,7 @@ func verticalSpacer(height string) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var16).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -652,7 +656,7 @@ func newsletterRegistration(page *controller.Page) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("emailSubscribe")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 271, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 271, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -771,7 +775,7 @@ func qaIndividualSection(qa types.QAItem) templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(qa.Question) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 321, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 321, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -784,7 +788,7 @@ func qaIndividualSection(qa types.QAItem) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(qa.Answer) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 330, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 330, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -826,7 +830,7 @@ func madeWithLove(d types.LandingPage) templ.Component { var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs("Made With Sprinkles of Love and Tears of Frustration.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 357, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 357, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -865,8 +869,12 @@ func socialMedia(d types.LandingPage) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var28 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", d.ContactEmail)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var28))) + var templ_7745c5c3_Var28 templ.SafeURL + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("mailto:%s", d.ContactEmail))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 452, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -915,7 +923,7 @@ func questionExample(questionType, prompt string) templ.Component { var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var30).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { @@ -937,7 +945,7 @@ func questionExample(questionType, prompt string) templ.Component { var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var32).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { @@ -965,7 +973,7 @@ func questionExample(questionType, prompt string) templ.Component { var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(prompt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/landing_page.templ`, Line: 498, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/landing_page.templ`, Line: 498, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { diff --git a/templates/pages/login.templ b/app/goship/views/pages/login.templ similarity index 98% rename from templates/pages/login.templ rename to app/goship/views/pages/login.templ index 7c1215b6..6d82bda4 100644 --- a/templates/pages/login.templ +++ b/app/goship/views/pages/login.templ @@ -3,7 +3,7 @@ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/routing/routenames" ) diff --git a/templates/pages/login_templ.go b/app/goship/views/pages/login_templ.go similarity index 83% rename from templates/pages/login_templ.go rename to app/goship/views/pages/login_templ.go index 7ebaf2a0..d7d8a577 100644 --- a/templates/pages/login_templ.go +++ b/app/goship/views/pages/login_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.865 +// templ: version: v0.3.898 package pages //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -9,10 +9,10 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( + "github.com/mikestefanello/pagoda/app/goship/views/components" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/components" ) func Login(page *controller.Page) templ.Component { @@ -71,8 +71,12 @@ func login(page *controller.Page, form *types.LoginForm) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(page.ToURL("login.submit")) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3))) + var templ_7745c5c3_Var3 templ.SafeURL + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL("login.submit"))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/login.templ`, Line: 19, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -92,7 +96,7 @@ func login(page *controller.Page, form *types.LoginForm) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/login.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/login.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -105,7 +109,7 @@ func login(page *controller.Page, form *types.LoginForm) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages/login.templ`, Line: 30, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/pages/login.templ`, Line: 30, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -119,14 +123,14 @@ func login(page *controller.Page, form *types.LoginForm) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
    0 { + firebaseJSONAccessKeys = &c.Config.App.FirebaseJSONAccessKeys + } + fcmPushNotificationsRepo, err := notifierrepo.NewFcmPushNotificationsRepo(c.ORM, firebaseJSONAccessKeys) if err != nil { - log.Fatal().Err(err) + return fmt.Errorf("build fcm notifications repo: %w", err) } - notificationSendPermissionRepo := notifierrepo.NewNotificationSendPermissionRepo(c.ORM) - // notifierRepo := notifierrepo.NewNotifierRepo( - // pubsubRepo, notificationStorageRepo, pwaPushNotificationsRepo, fcmPushNotificationsRepo, profileRepo.GetCountOfUnseenNotifications) + region := strings.TrimSpace(c.Config.Phone.Region) + if region == "" { + region = "us-east-1" + } smsSenderRepo, err := notifierrepo.NewSMSSender( - c.ORM, c.Config.Phone.Region, c.Config.Phone.SenderID, c.Config.Phone.ValidationCodeExpirationMinutes) + c.ORM, + region, + c.Config.Phone.SenderID, + c.Config.Phone.ValidationCodeExpirationMinutes, + ) if err != nil { - log.Fatal().Err(err) + return fmt.Errorf("build sms sender repo: %w", err) } - // The onboarding group is for all pages that should be accessible during onboarding. - // We use middleware in the other authenticated routes to redirect to the onboarding - // flow if the user has not completed it. onboardingGroup := g.Group("/welcome", middleware.RequireAuthentication()) preferences := NewPreferencesRoute( - ctr, &profileRepo, pwaPushNotificationsRepo, notificationSendPermissionRepo, subscriptionsRepo, smsSenderRepo) + ctr, + deps.profileRepo, + pwaPushNotificationsRepo, + deps.notificationSendPermissionRepo, + deps.subscriptionsRepo, + smsSenderRepo, + ) onboardingGroup.GET("/preferences", preferences.Get).Name = routeNames.RouteNamePreferences onboardingGroup.GET("/preferences/phone", preferences.GetPhoneComponent).Name = routeNames.RouteNameGetPhone onboardingGroup.GET("/preferences/phone/verification", preferences.GetPhoneVerificationComponent).Name = routeNames.RouteNameGetPhoneVerification @@ -358,11 +356,10 @@ func coreAuthRoutes(c *services.Container, g *echo.Group, ctr controller.Control onboardingGroup.GET("/preferences/display-name/get", preferences.GetDisplayName).Name = routeNames.RouteNameGetDisplayName onboardingGroup.POST("/preferences/display-name/save", preferences.SaveDisplayName).Name = routeNames.RouteNameUpdateDisplayName - deleteAccountRoute := NewDeleteAccountRoute(ctr, &profileRepo, subscriptionsRepo) + deleteAccountRoute := NewDeleteAccountRoute(ctr, deps.profileRepo, deps.subscriptionsRepo) onboardingGroup.GET("/preferences/delete-account", deleteAccountRoute.DeleteAccountPage).Name = routeNames.RouteNameDeleteAccountPage onboardingGroup.GET("/preferences/delete-account/now", deleteAccountRoute.DeleteAccountRequest).Name = routeNames.RouteNameDeleteAccountRequest - // TODO: move all pref routes to the preferences route (and not have a gazillion different ..) finishOnboarding := NewOnboardingRoute(ctr, c.ORM, c.Tasks) onboardingGroup.GET("/finish-onboarding", finishOnboarding.Get).Name = routeNames.RouteNameFinishOnboarding @@ -370,69 +367,67 @@ func coreAuthRoutes(c *services.Container, g *echo.Group, ctr controller.Control onboardingGroup.GET("/profileBio", profilePrefs.GetBio).Name = routeNames.RouteNameGetBio onboardingGroup.POST("/profileBio/update", profilePrefs.UpdateBio).Name = routeNames.RouteNameUpdateBio - outgoingNotifications := NewPushNotifsRoute(ctr, pwaPushNotificationsRepo, fcmPushNotificationsRepo, notificationSendPermissionRepo) + outgoingNotifications := NewPushNotifsRoute(ctr, pwaPushNotificationsRepo, fcmPushNotificationsRepo, deps.notificationSendPermissionRepo) onboardingGroup.GET("/subscription/push", outgoingNotifications.GetPushSubscriptions).Name = routeNames.RouteNameGetPushSubscriptions onboardingGroup.POST("/subscription/:platform", outgoingNotifications.RegisterSubscription).Name = routeNames.RouteNameRegisterSubscription onboardingGroup.DELETE("/subscription/:platform", outgoingNotifications.DeleteSubscription).Name = routeNames.RouteNameDeleteSubscription onboardingGroup.GET("/email-subscription/unsubscribe/:permission/:token", outgoingNotifications.DeleteEmailSubscription).Name = routeNames.RouteNameDeleteEmailSubscriptionWithToken - // The "all group" is for routes that need to have an authenticated but do not need an onboarded profile allGroup := g.Group("/auth", middleware.RequireAuthentication()) logout := NewLogoutRoute(ctr) allGroup.GET("/logout", logout.Get, middleware.RequireAuthentication()).Name = routeNames.RouteNameLogout - // Auth group is for all routes that are accessible to a fully logged in and onboarded user onboardedGroup := g.Group("/auth", middleware.RequireAuthentication(), middleware.RedirectToOnboardingIfNotComplete()) verifyEmail := NewVerifyEmailRoute(ctr) g.GET("/email/verify/:token", verifyEmail.Get).Name = routeNames.RouteNameVerifyEmail - homeFeed := NewHomeFeedRoute(ctr, profileRepo, &c.Config.App.PageSize) + homeFeed := NewHomeFeedRoute(ctr, *deps.profileRepo, &c.Config.App.PageSize) onboardedGroup.GET("/homeFeed", homeFeed.Get, middleware.SetLastSeenOnline(c.Auth)).Name = routeNames.RouteNameHomeFeed onboardedGroup.GET("/homeFeed/buttons", homeFeed.GetHomeButtons).Name = routeNames.RouteNameGetHomeFeedButtons - singleProfile := NewProfileRoutes(ctr, &profileRepo) + singleProfile := NewProfileRoutes(ctr, deps.profileRepo) onboardedGroup.GET("/profile", singleProfile.Get).Name = routeNames.RouteNameProfile - uploadPhoto := NewUploadPhotoRoutes(ctr, &profileRepo, storageRepo, c.Config.Storage.PhotosMaxFileSizeMB) - onboardedGroup.GET("/uploadPhoto", uploadPhoto.Get).Name = "uploadPhoto" - onboardedGroup.POST("/uploadPhoto", uploadPhoto.Post).Name = "uploadPhoto.post" - onboardedGroup.DELETE("/uploadPhoto/:image_id", uploadPhoto.Delete).Name = "uploadPhoto.delete" - - currProfilePhoto := NewCurrProfilePhotoRoutes(ctr, &profileRepo, storageRepo, c.Config.Storage.PhotosMaxFileSizeMB) - onboardedGroup.GET("/currProfilePhoto", currProfilePhoto.Get).Name = "currProfilePhoto" - onboardedGroup.POST("/currProfilePhoto", currProfilePhoto.Post).Name = "currProfilePhoto.post" + uploadPhoto := NewUploadPhotoRoutes(ctr, deps.profileRepo, deps.storageRepo, c.Config.Storage.PhotosMaxFileSizeMB) + onboardedGroup.GET("/uploadPhoto", uploadPhoto.Get).Name = routeNames.RouteNameUploadPhoto + onboardedGroup.POST("/uploadPhoto", uploadPhoto.Post).Name = routeNames.RouteNameUploadPhotoSubmit + onboardedGroup.DELETE("/uploadPhoto/:image_id", uploadPhoto.Delete).Name = routeNames.RouteNameUploadPhotoDelete - // // TODO: create functions to create these Removfe notifierRepo as it's accessible on container. - // markNormalNotificationRead := NewMarkNormalNotificationReadRoute(ctr, notifierRepo) - // onboardedGroup.POST("/notificationSeenByEvent/:notification_id", markNormalNotificationRead.Post).Name = routeNames.RouteNameMarkNotificationsAsRead + currProfilePhoto := NewCurrProfilePhotoRoutes(ctr, deps.profileRepo, deps.storageRepo, c.Config.Storage.PhotosMaxFileSizeMB) + onboardedGroup.GET("/currProfilePhoto", currProfilePhoto.Get).Name = routeNames.RouteNameCurrentProfilePhoto + onboardedGroup.POST("/currProfilePhoto", currProfilePhoto.Post).Name = routeNames.RouteNameCurrentProfilePhotoSubmit - // markNormalNotificationUnread := NewMarkNormalNotificationUnreadRoute(ctr, notifierRepo) - // onboardedGroup.POST("/markNormalNotificationUnread", markNormalNotificationUnread.Post).Name = "markNormalNotificationUnread" + normalNotificationsCount := NewNormalNotificationsCountRoute(ctr, *deps.profileRepo) + onboardedGroup.GET("/notifications/normalNotificationsCount", normalNotificationsCount.Get).Name = routeNames.RouteNameNormalNotificationsCount - normalNotificationsCount := NewNormalNotificationsCountRoute(ctr, profileRepo) - onboardedGroup.GET("/notifications/normalNotificationsCount", normalNotificationsCount.Get).Name = "normalNotificationsCount" - - // normalNotifications := NewNormalNotificationsRoute(ctr, notifierRepo) - // onboardedGroup.GET("/notifications", normalNotifications.Get, middleware.SetLastSeenOnline(c.Auth)).Name = "normalNotifications" - // onboardedGroup.GET("/notifications/markAllAsRead", normalNotifications.MarkAllAsRead, middleware.SetLastSeenOnline(c.Auth)).Name = routeNames.RouteNameMarkAllNotificationsAsRead - - // onboardedGroup.DELETE("/notifications/normalNotifications/:notification_id", normalNotifications.Delete).Name = "normalNotifications.delete" - - payments := NewPaymentsRoute(ctr, c.ORM, subscriptionsRepo) + payments := NewPaymentsRoute(ctr, c.ORM, deps.subscriptionsRepo) onboardedGroup.GET("/payments/get-public-key", payments.GetPaymentProcessorPublickey).Name = routeNames.RouteNamePaymentProcessorGetPublicKey onboardedGroup.POST("/payments/create-checkout-session", payments.CreateCheckoutSession).Name = routeNames.RouteNameCreateCheckoutSession onboardedGroup.POST("/payments/create-portal-session", payments.CreatePortalSession).Name = routeNames.RouteNameCreatePortalSession onboardedGroup.GET("/payments/pricing", payments.PricingPage).Name = routeNames.RouteNamePricingPage onboardedGroup.GET("/payments/success", payments.SuccessfullySubscribed).Name = routeNames.RouteNamePaymentProcessorSuccess + return nil } -// sseRoutes because they have no read timeout set on them -func sseRoutes(c *services.Container, g *echo.Group, ctr controller.Controller) { +// ==================== External Domain ==================== - onboardedGroup := g.Group("/auth", middleware.RequireAuthentication()) +func registerExternalDomainRoutes(c *services.Container, g *echo.Group, ctr controller.Controller, deps *routeDeps) { + payments := NewPaymentsRoute(ctr, c.ORM, deps.subscriptionsRepo) + g.POST(deps.stripeWebhookPath, payments.HandleWebhook).Name = routeNames.RouteNamePaymentProcessorWebhook +} +// ==================== Realtime Domain ==================== + +// registerRealtimeDomainRoutes wires SSE routes because they have no read timeout set. +func registerRealtimeDomainRoutes(c *services.Container, g *echo.Group, ctr controller.Controller) error { + if c.Notifier == nil { + return errors.New("cannot register realtime routes: notifier is nil") + } + + onboardedGroup := g.Group("/auth", middleware.RequireAuthentication()) realtime := NewRealtimeRoute(ctr, *c.Notifier) onboardedGroup.GET("/realtime", realtime.Get).Name = routeNames.RouteNameRealtime + return nil } diff --git a/pkg/routing/routes/routes_test.go b/app/goship/web/routes/routes_test.go similarity index 95% rename from pkg/routing/routes/routes_test.go rename to app/goship/web/routes/routes_test.go index 97c7d912..c5763829 100644 --- a/pkg/routing/routes/routes_test.go +++ b/app/goship/web/routes/routes_test.go @@ -23,6 +23,10 @@ var ( ) func TestMain(m *testing.M) { + if err := os.Chdir("../../../.."); err != nil { + panic(err) + } + // Set the environment to test config.SwitchEnvironment(config.EnvTest) @@ -30,7 +34,9 @@ func TestMain(m *testing.M) { c = services.NewContainer() // Start a test HTTP server - BuildRouter(c) + if err := BuildRouter(c); err != nil { + panic(err) + } srv = httptest.NewServer(c.Web) // Run tests diff --git a/pkg/routing/routes/upload_photo.go b/app/goship/web/routes/upload_photo.go similarity index 98% rename from pkg/routing/routes/upload_photo.go rename to app/goship/web/routes/upload_photo.go index 033453b3..19103e0b 100644 --- a/pkg/routing/routes/upload_photo.go +++ b/app/goship/web/routes/upload_photo.go @@ -6,13 +6,13 @@ import ( "net/http" "strconv" + "github.com/mikestefanello/pagoda/app/goship/views/layouts" "github.com/mikestefanello/pagoda/ent" "github.com/mikestefanello/pagoda/pkg/context" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/repos/profilerepo" storagerepo "github.com/mikestefanello/pagoda/pkg/repos/storage" "github.com/mikestefanello/pagoda/pkg/routing/routenames" - "github.com/mikestefanello/pagoda/templates/layouts" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" diff --git a/pkg/routing/routes/verify_email.go b/app/goship/web/routes/verify_email.go similarity index 100% rename from pkg/routing/routes/verify_email.go rename to app/goship/web/routes/verify_email.go diff --git a/pkg/routing/routes/verify_email_subscription.go b/app/goship/web/routes/verify_email_subscription.go similarity index 95% rename from pkg/routing/routes/verify_email_subscription.go rename to app/goship/web/routes/verify_email_subscription.go index 761bcbb1..5a32be7e 100644 --- a/pkg/routing/routes/verify_email_subscription.go +++ b/app/goship/web/routes/verify_email_subscription.go @@ -2,9 +2,9 @@ package routes import ( "github.com/labstack/echo/v4" + "github.com/mikestefanello/pagoda/app/goship/views/layouts" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/repos/emailsmanager" - "github.com/mikestefanello/pagoda/templates/layouts" ) type verifyEmailSubscription struct { diff --git a/cmd/web/main.go b/cmd/web/main.go index 70b0e21c..6dbc1b8e 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -10,7 +10,7 @@ import ( "os/signal" "time" - "github.com/mikestefanello/pagoda/pkg/routing/routes" + "github.com/mikestefanello/pagoda/app/goship/web/routes" "github.com/mikestefanello/pagoda/pkg/services" ) @@ -40,7 +40,9 @@ func main() { }() // Build the router - routes.BuildRouter(c) + if err := routes.BuildRouter(c); err != nil { + c.Web.Logger.Fatalf("failed to build router: %v", err) + } // Start the server go func() { diff --git a/cmd/worker/main.go b/cmd/worker/main.go index 64c77ff8..d5b53720 100644 --- a/cmd/worker/main.go +++ b/cmd/worker/main.go @@ -3,14 +3,15 @@ package main import ( "fmt" "log" + "strings" "github.com/hibiken/asynq" + "github.com/mikestefanello/pagoda/app/goship/web/routes" "github.com/mikestefanello/pagoda/config" "github.com/mikestefanello/pagoda/pkg/repos/notifierrepo" "github.com/mikestefanello/pagoda/pkg/repos/profilerepo" storagerepo "github.com/mikestefanello/pagoda/pkg/repos/storage" "github.com/mikestefanello/pagoda/pkg/repos/subscriptions" - "github.com/mikestefanello/pagoda/pkg/routing/routes" "github.com/mikestefanello/pagoda/pkg/services" "github.com/mikestefanello/pagoda/pkg/tasks" ) @@ -23,9 +24,20 @@ func main() { } // Build the worker server + cacheHost := strings.TrimSpace(cfg.Cache.Hostname) + if cacheHost == "" || strings.EqualFold(cacheHost, "FILL") { + log.Printf("cache hostname is unset/placeholder (%q); defaulting to localhost for local worker", cfg.Cache.Hostname) + cacheHost = "localhost" + } + + cachePort := cfg.Cache.Port + if cachePort == 0 { + cachePort = 6379 + } + srv := asynq.NewServer( asynq.RedisClientOpt{ - Addr: fmt.Sprintf("%s:%d", cfg.Cache.Hostname, cfg.Cache.Port), + Addr: fmt.Sprintf("%s:%d", cacheHost, cachePort), DB: cfg.Cache.Database, Password: cfg.Cache.Password, }, @@ -49,7 +61,9 @@ func main() { }() // Build the router, which is needed to get the reverse of routes by name in some tasks. - routes.BuildRouter(c) + if err := routes.BuildRouter(c); err != nil { + c.Web.Logger.Fatalf("failed to build router: %v", err) + } storageRepo := storagerepo.NewStorageClient(c.Config, c.ORM) subscriptionsRepo := subscriptions.NewSubscriptionsRepo( diff --git a/config/config.go b/config/config.go index 10e15e01..d2e02ec8 100644 --- a/config/config.go +++ b/config/config.go @@ -91,10 +91,10 @@ type ( } ProcessesConfig struct { - Web bool - Worker bool - Scheduler bool - CoLocated bool + Web bool + Worker bool + Scheduler bool + CoLocated bool } AdaptersConfig struct { @@ -141,6 +141,7 @@ type ( PublicStripeKey string PrivateStripeKey string StripeWebhookSecret string + StripeWebhookPath string AppEncryptionKey string FirebaseBase64AccessKeys string FirebaseJSONAccessKeys []byte diff --git a/config/config.yaml b/config/config.yaml index cc4d2bc8..3e41ef4f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -42,6 +42,7 @@ app: publicStripeKey: "pk_..." privateStripeKey: "sk_..." stripeWebhookSecret: "whsec_..." + stripeWebhookPath: "/Q2HBfAY7iid59J1SUN8h1Y3WxJcPWA/payments/webhooks" vapidPublicKey: "" vapidPrivateKey: "" sentryDsn: "my-sentry-dsn-in-config" diff --git a/docs/README.md b/docs/README.md index 04d5fd4a..67bca1ef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ It is not intended as user-facing product documentation. - `project-scope-analysis.md`: End-to-end feature and capability analysis. - `architecture.md`: Runtime architecture, request flow, and service composition. +- `structure-and-boundaries.md`: Canonical placement rules for app vs framework code. - `http-routes.md`: Route inventory grouped by access level and purpose. - `data-model.md`: Ent entities and domain model coverage. - `ai-agent-guide.md`: Practical guide for AI agents working in this repo. @@ -25,8 +26,8 @@ It is not intended as user-facing product documentation. - `cmd/worker/main.go` - `cmd/seed/main.go` - `pkg/services/container.go` -- `pkg/routing/routes/router.go` -- `pkg/routing/routes/*.go` +- `app/goship/web/routes/router.go` +- `app/goship/web/routes/*.go` - `pkg/tasks/*.go` - `pkg/repos/**/*.go` - `ent/schema/*.go` diff --git a/docs/ai-agent-guide.md b/docs/ai-agent-guide.md index 4c7e196a..49e7f4a7 100644 --- a/docs/ai-agent-guide.md +++ b/docs/ai-agent-guide.md @@ -5,12 +5,12 @@ This guide is for code agents making changes in this repository. ## Start Here 1. Read `docs/project-scope-analysis.md` and `docs/known-gaps-and-risks.md`. -2. Inspect route wiring in `pkg/routing/routes/router.go` before editing handlers. +2. Inspect route wiring in `app/goship/web/routes/router.go` before editing handlers. 3. Inspect `pkg/services/container.go` before assuming a dependency is initialized. ## Architectural Conventions -- HTTP handlers live in `pkg/routing/routes`. +- HTTP handlers live in `app/goship/web/routes`. - Domain logic should prefer repository packages (`pkg/repos/...`) over route-level DB logic. - Rendering is typically done via `controller.Page` + templ components. - Enums/constants are centralized in `pkg/domain`. @@ -25,7 +25,7 @@ This guide is for code agents making changes in this repository. 2. Check for related tests: - `rg "func Test" pkg/...` -- route tests in `pkg/routing/routes/*_test.go` +- route tests in `app/goship/web/routes/*_test.go` 3. Implement minimal, local change first. 4. Run targeted tests, then broader tests if needed. @@ -47,7 +47,7 @@ Dependency wiring: Routing and middleware: -- `pkg/routing/routes/router.go` +- `app/goship/web/routes/router.go` - `pkg/middleware/*.go` Data and domain: @@ -59,7 +59,7 @@ Data and domain: UI and rendering: - `pkg/controller/*.go` -- `templates/**/*.templ` +- `app/goship/views/**/*.templ` - `javascript/**/*` ## Common Pitfalls @@ -86,4 +86,3 @@ When code behavior changes, update at least: - `docs/project-scope-analysis.md` if capability changed - `docs/http-routes.md` if route surface changed - `docs/known-gaps-and-risks.md` if a risk was added/removed - diff --git a/docs/architecture.md b/docs/architecture.md index c6466bb0..fb151485 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,18 +6,18 @@ The application follows a layered structure: - `cmd/*`: process entrypoints (`web`, `worker`, `seed`) - `pkg/services`: dependency container and infrastructure clients -- `pkg/routing/routes`: HTTP handlers and route composition +- `app/goship/web/routes`: HTTP handlers and route composition - `pkg/middleware`: auth/session/cache/onboarding/request middleware - `pkg/repos`: data access and external service adapters - `pkg/controller`: rendering, page object, redirect helpers -- `templates`: Templ UI components/layouts/pages/emails +- `app/goship/views`: Templ UI components/layouts/pages/emails - `ent`: schema + generated ORM - `pkg/tasks`: Asynq task processors ## Web Runtime Flow 1. `cmd/web/main.go` creates container via `services.NewContainer()`. -2. `routes.BuildRouter(c)` configures middleware stack and registers routes. +2. `routes.BuildRouter(c)` configures middleware stack, registers routes, and returns an error on startup misconfiguration. 3. Echo server starts with request timeout middleware (SSE-aware). 4. Request path executes middleware chain, route handler, and page rendering. @@ -53,7 +53,7 @@ This mismatch affects parts of the runtime that assume those dependencies exist. ## HTTP Middleware Stack -Primary middleware set in `pkg/routing/routes/router.go` includes: +Primary middleware set in `app/goship/web/routes/router.go` includes: - Trailing slash normalization - Panic recovery @@ -80,8 +80,8 @@ The UI is server-rendered using Templ components. - Base page abstraction: `pkg/controller/page.go` - Render orchestration: `pkg/controller/controller.go` -- Layout wrappers: `templates/layouts/*.templ` -- Route page components: `templates/pages/*.templ` +- Layout wrappers: `app/goship/views/layouts/*.templ` +- Route page components: `app/goship/views/pages/*.templ` HTMX behavior is integrated in the page object (`Page.HTMX`) and controller render logic. @@ -98,7 +98,7 @@ HTMX behavior is integrated in the page object (`Page.HTMX`) and controller rend - persistent DB notifications - pub/sub events for SSE - push channels (PWA + FCM) -- SSE endpoint exists (`pkg/routing/routes/realtime.go`) but route wiring is currently disabled. +- SSE endpoint exists (`app/goship/web/routes/realtime.go`) but route wiring is currently disabled. ## Frontend Asset Architecture @@ -112,4 +112,3 @@ HTMX behavior is integrated in the page object (`Page.HTMX`) and controller rend - Local process orchestration via Overmind (`Procfile`) - Docker Compose for Redis + Mailpit in current config - Kamal deployment files present (`deploy/`, `.kamal/`) - diff --git a/docs/http-routes.md b/docs/http-routes.md index c1287a41..0df6ae05 100644 --- a/docs/http-routes.md +++ b/docs/http-routes.md @@ -1,6 +1,6 @@ # HTTP Route Map -Routes are registered in `pkg/routing/routes/router.go`. +Routes are registered in `app/goship/web/routes/router.go`. ## Public/General Routes diff --git a/docs/known-gaps-and-risks.md b/docs/known-gaps-and-risks.md index dd1a6e6e..e386631c 100644 --- a/docs/known-gaps-and-risks.md +++ b/docs/known-gaps-and-risks.md @@ -19,9 +19,9 @@ Impact: ## 2) Realtime SSE Route Not Wired (High for realtime features) -`pkg/routing/routes/realtime.go` has a full SSE handler, but router registration is commented out: +`app/goship/web/routes/realtime.go` has a full SSE handler, but router registration is commented out: -- `sseRoutes(c, s, ctr)` is commented in `pkg/routing/routes/router.go`. +- `sseRoutes(c, s, ctr)` is commented in `app/goship/web/routes/router.go`. Impact: @@ -29,7 +29,7 @@ Impact: ## 3) Notification Center Endpoints Partially Disabled (Medium) -Route handlers exist in `pkg/routing/routes/notifications.go`, but several are commented out during route wiring. +Route handlers exist in `app/goship/web/routes/notifications.go`, but several are commented out during route wiring. Impact: @@ -64,7 +64,7 @@ Impact: ## 7) Some Feature Paths Still Use Placeholder Data (Low) -Example: home feed button counts are hardcoded in `pkg/routing/routes/home_feed.go`. +Example: home feed button counts are hardcoded in `app/goship/web/routes/home_feed.go`. Impact: diff --git a/docs/project-scope-analysis.md b/docs/project-scope-analysis.md index ad21bae0..c9349399 100644 --- a/docs/project-scope-analysis.md +++ b/docs/project-scope-analysis.md @@ -27,7 +27,7 @@ The repository still carries heritage from a related product domain ("Cherie"), Core flows implemented in routes and services: -- Login/logout (`pkg/routing/routes/login.go`, `logout.go`) +- Login/logout (`app/goship/web/routes/login.go`, `logout.go`) - Register (`register.go`) - Forgot/reset password (`forgot_password.go`, `reset_password.go`) - Email verification (`verify_email.go`) @@ -41,14 +41,14 @@ Key implementation choices: ## 2) Onboarding, Preferences, and Profile -- Onboarding and preferences mostly in `pkg/routing/routes/preferences.go` +- Onboarding and preferences mostly in `app/goship/web/routes/preferences.go` - Profile page in `profile.go` - Mark onboarding completion (`/welcome/finish-onboarding`) - Profile photo and gallery image routes (`profile_photo.go`, `upload_photo.go`) ## 3) Payments and Subscription Lifecycle -- Stripe checkout + customer portal + webhook in `pkg/routing/routes/payments.go` +- Stripe checkout + customer portal + webhook in `app/goship/web/routes/payments.go` - Local subscription state managed in `pkg/repos/subscriptions/subscriptions.go` - Product model currently centered on free vs pro (`pkg/domain/enum.go`) @@ -101,7 +101,7 @@ Worker bootstrap and registration in `cmd/worker/main.go`. ## 8) Frontend Delivery Model -- Server-rendered pages via Templ (`templates/` + `pkg/controller`) +- Server-rendered pages via Templ (`app/goship/views/` + `pkg/controller`) - HTMX-enhanced interactions - Optional Svelte components bundled into `static/svelte_bundle.js` - Optional vanilla JS bundle into `static/vanilla_bundle.js` @@ -137,4 +137,3 @@ Storage modes: ## Practical Summary This codebase is a strong "production-ready starter" foundation with authentication, payments, notifications, storage, and worker primitives. It is also in an active transitional state where some features are scaffolded but not fully wired in the web runtime. - diff --git a/docs/structure-and-boundaries.md b/docs/structure-and-boundaries.md new file mode 100644 index 00000000..ecc9d8a0 --- /dev/null +++ b/docs/structure-and-boundaries.md @@ -0,0 +1,49 @@ +# Structure and Boundaries + +This document defines where code belongs as GoShip evolves into a Rails-like framework plus example app. + +## Current Top-Level Shape + +- `app/goship/`: app-specific code for the first-party GoShip app +- `cmd/`: runnable entrypoints (`web`, `worker`, `seed`, CLI) +- `pkg/`: reusable framework-level libraries and adapters +- `config/`: runtime configuration +- `ent/`: schema and generated ORM +- `docs/`: internal design and implementation documentation + +## App vs Framework Rules + +Use this placement rule for every new file: + +- Put code in `app/goship/...` when it encodes product behavior/UI for the GoShip app. +- Put code in `pkg/...` when it is reusable as framework infrastructure across apps. + +## Web Layer Layout + +App web code is now app-scoped: + +- `app/goship/web/routes`: route composition + handlers +- `app/goship/views`: templ components/layouts/pages/emails + +Router source of truth: + +- `app/goship/web/routes/router.go` + +## Refactor Status + +Completed in this pass: + +- Moved routes from `pkg/routing/routes` to `app/goship/web/routes`. +- Moved templ views from `templates` to `app/goship/views`. +- Updated imports and test package paths accordingly. + +Still intentionally centralized (next phase): + +- `pkg/repos` +- `pkg/services` + +These remain framework-level until each package is classified as either: + +- app-specific (move under `app/goship/...`), or +- reusable framework module (stay in `pkg/...` or move to future dedicated framework modules). + diff --git a/go.mod b/go.mod index 80135174..8b6bd58b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/PuerkitoBio/goquery v1.8.1 github.com/SherClockHolmes/webpush-go v1.3.0 - github.com/a-h/templ v0.3.865 + github.com/a-h/templ v0.3.1001 github.com/aws/aws-sdk-go-v2 v1.18.1 github.com/aws/aws-sdk-go-v2/config v1.18.27 github.com/aws/aws-sdk-go-v2/service/sns v1.20.13 @@ -41,12 +41,12 @@ require ( github.com/rs/zerolog v1.29.1 github.com/samber/slog-echo v1.12.1 github.com/spf13/viper v1.17.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/stripe/stripe-go/v78 v78.6.0 github.com/testcontainers/testcontainers-go v0.29.1 github.com/testcontainers/testcontainers-go/modules/postgres v0.29.1 github.com/ziflex/lecho/v3 v3.5.0 - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.40.0 golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 google.golang.org/api v0.183.0 ) @@ -193,14 +193,14 @@ require ( go.opentelemetry.io/otel/trace v1.27.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/image v0.15.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.39.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.42.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.32.0 // indirect + golang.org/x/tools v0.35.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect diff --git a/go.sum b/go.sum index c0385b76..f56271e5 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ github.com/SherClockHolmes/webpush-go v1.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKl github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw= github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10= github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY= -github.com/a-h/templ v0.3.865 h1:nYn5EWm9EiXaDgWcMQaKiKvrydqgxDUtT1+4zU2C43A= -github.com/a-h/templ v0.3.865/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ= +github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= +github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= @@ -582,8 +582,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stripe/stripe-go/v78 v78.6.0 h1:UZ8G45Nd/jOFGvKGSpuc2SWobcH5Jttrmy3xlu7TAIc= github.com/stripe/stripe-go/v78 v78.6.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -660,8 +660,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -679,8 +679,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -703,8 +703,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -717,8 +717,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -755,8 +755,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -772,8 +772,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -796,8 +796,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 457ad751..ef57d16a 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -7,15 +7,15 @@ import ( "os" "testing" + "github.com/mikestefanello/pagoda/app/goship/views/components" + "github.com/mikestefanello/pagoda/app/goship/views/layouts" + "github.com/mikestefanello/pagoda/app/goship/views/pages" "github.com/mikestefanello/pagoda/config" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/htmx" "github.com/mikestefanello/pagoda/pkg/middleware" "github.com/mikestefanello/pagoda/pkg/services" "github.com/mikestefanello/pagoda/pkg/tests" - "github.com/mikestefanello/pagoda/templates/components" - "github.com/mikestefanello/pagoda/templates/layouts" - "github.com/mikestefanello/pagoda/templates/pages" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/controller/page.go b/pkg/controller/page.go index e9572cd0..385682f4 100644 --- a/pkg/controller/page.go +++ b/pkg/controller/page.go @@ -8,12 +8,12 @@ import ( "github.com/a-h/templ" "github.com/labstack/echo/v4" echomw "github.com/labstack/echo/v4/middleware" + "github.com/mikestefanello/pagoda/app/goship/views" "github.com/mikestefanello/pagoda/ent" "github.com/mikestefanello/pagoda/pkg/context" "github.com/mikestefanello/pagoda/pkg/domain" "github.com/mikestefanello/pagoda/pkg/htmx" "github.com/mikestefanello/pagoda/pkg/repos/msg" - "github.com/mikestefanello/pagoda/templates" ) type ( diff --git a/pkg/htmx/htmx.go b/pkg/htmx/htmx.go index b14c76da..d689ffc6 100644 --- a/pkg/htmx/htmx.go +++ b/pkg/htmx/htmx.go @@ -6,7 +6,7 @@ import ( "github.com/labstack/echo/v4" ) -// Headers (https://htmx.org/docs/#requests) +// Headers (https://htmx.org/docs/#requests) const ( HeaderRequest = "HX-Request" HeaderBoosted = "HX-Boosted" diff --git a/pkg/repos/emailsmanager/update_email.go b/pkg/repos/emailsmanager/update_email.go index b54877e9..5268b47e 100644 --- a/pkg/repos/emailsmanager/update_email.go +++ b/pkg/repos/emailsmanager/update_email.go @@ -10,6 +10,8 @@ import ( mapset "github.com/deckarep/golang-set/v2" "github.com/labstack/echo/v4" + "github.com/mikestefanello/pagoda/app/goship/views/emails" + "github.com/mikestefanello/pagoda/app/goship/views/layouts" "github.com/mikestefanello/pagoda/ent" "github.com/mikestefanello/pagoda/ent/notification" "github.com/mikestefanello/pagoda/ent/notificationpermission" @@ -20,8 +22,6 @@ import ( "github.com/mikestefanello/pagoda/pkg/routing/routenames" "github.com/mikestefanello/pagoda/pkg/services" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/emails" - "github.com/mikestefanello/pagoda/templates/layouts" "github.com/rs/zerolog/log" ) diff --git a/pkg/routing/routenames/routenames.go b/pkg/routing/routenames/routenames.go index 109dca99..015ce311 100644 --- a/pkg/routing/routenames/routenames.go +++ b/pkg/routing/routenames/routenames.go @@ -30,8 +30,19 @@ const ( RouteNameGetHomeFeedButtons = "home_feed.buttons" RouteNameGetHomeFeedStats = "home_feed.stats" - RouteNameProfile = "profile" - RouteNameInstallApp = "install_app" + RouteNameProfile = "profile" + RouteNameInstallApp = "install_app" + RouteNameClearCookie = "clear_cookie" + RouteNameHealthcheck = "healthcheck" + RouteNameEmailSubscribe = "email_subscribe" + RouteNameEmailSubscribeSubmit = "email_subscribe.submit" + RouteNameVerifyEmailSubscription = "verify_email_subscription" + RouteNameUploadPhoto = "upload_photo" + RouteNameUploadPhotoSubmit = "upload_photo.submit" + RouteNameUploadPhotoDelete = "upload_photo.delete" + RouteNameCurrentProfilePhoto = "current_profile_photo" + RouteNameCurrentProfilePhotoSubmit = "current_profile_photo.submit" + RouteNameNormalNotificationsCount = "normal_notifications_count" RouteNameMarkNotificationsAsRead = "markNormalNotificationRead" RouteNameMarkAllNotificationsAsRead = "normalNotificationsMarkAllAsRead" diff --git a/pkg/runtimeplan/features.go b/pkg/runtimeplan/features.go index c96a1700..bd9a613c 100644 --- a/pkg/runtimeplan/features.go +++ b/pkg/runtimeplan/features.go @@ -17,4 +17,3 @@ func ResolveWebFeatures(plan Plan, hasCache, hasNotifier bool) WebFeatures { EnableRealtime: hasNotifier && plan.Adapters.PubSub != "", } } - diff --git a/pkg/runtimeplan/features_test.go b/pkg/runtimeplan/features_test.go index f46ec141..e0763ae9 100644 --- a/pkg/runtimeplan/features_test.go +++ b/pkg/runtimeplan/features_test.go @@ -77,4 +77,3 @@ func TestResolveWebFeatures(t *testing.T) { }) } } - diff --git a/pkg/runtimeplan/plan.go b/pkg/runtimeplan/plan.go index a7b068ab..25da4779 100644 --- a/pkg/runtimeplan/plan.go +++ b/pkg/runtimeplan/plan.go @@ -64,4 +64,3 @@ func Resolve(cfg *config.Config) (Plan, error) { return p, nil } - diff --git a/pkg/runtimeplan/plan_test.go b/pkg/runtimeplan/plan_test.go index e22ddf4c..9e500667 100644 --- a/pkg/runtimeplan/plan_test.go +++ b/pkg/runtimeplan/plan_test.go @@ -94,8 +94,8 @@ func TestResolve(t *testing.T) { Profile: config.RuntimeProfileDistributed, }, Processes: config.ProcessesConfig{ - Web: true, - Worker: true, + Web: true, + Worker: true, CoLocated: true, }, Adapters: config.AdaptersConfig{ @@ -126,4 +126,3 @@ func TestResolve(t *testing.T) { }) } } - diff --git a/pkg/tasks/mail.go b/pkg/tasks/mail.go index d28ad76b..19bfd907 100644 --- a/pkg/tasks/mail.go +++ b/pkg/tasks/mail.go @@ -7,6 +7,8 @@ import ( "github.com/hibiken/asynq" "github.com/labstack/echo/v4" + "github.com/mikestefanello/pagoda/app/goship/views/emails" + "github.com/mikestefanello/pagoda/app/goship/views/layouts" "github.com/mikestefanello/pagoda/config" "github.com/mikestefanello/pagoda/ent" "github.com/mikestefanello/pagoda/pkg/controller" @@ -14,8 +16,6 @@ import ( "github.com/mikestefanello/pagoda/pkg/repos/mailer" "github.com/mikestefanello/pagoda/pkg/services" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/templates/emails" - "github.com/mikestefanello/pagoda/templates/layouts" ) // //////////////////////////////////////////////////////////////////////////// diff --git a/scripts/dev.sh b/scripts/dev.sh index 83bdad49..819104f1 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -6,10 +6,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${SCRIPT_DIR}/up.sh" "${1:-}" -npm install -npm run build -npx tailwindcss -i ./styles/styles.css -o ./static/styles_bundle.css - -echo "Tip: run 'nvm use v18.20.7' if JS tooling fails." -overmind start - +echo "Starting JS-free dev mode (Go processes only)." +echo "Use 'make dev-full' if you want JS/CSS watchers enabled." +overmind start -f Procfile.dev diff --git a/scripts/test/integration-packages.txt b/scripts/test/integration-packages.txt index 2b697bb1..9c8289e4 100644 --- a/scripts/test/integration-packages.txt +++ b/scripts/test/integration-packages.txt @@ -1,5 +1,5 @@ # Packages with infrastructure-heavy tests -./pkg/routing/routes +./app/goship/web/routes ./pkg/repos/emailsmanager ./pkg/repos/notifierrepo ./pkg/repos/profilerepo diff --git a/static/meta_svelte_bundle.json b/static/meta_svelte_bundle.json index ec1cb9f9..029d3880 100644 --- a/static/meta_svelte_bundle.json +++ b/static/meta_svelte_bundle.json @@ -1 +1 @@ -{"inputs":{"node_modules/wc-toast/src/toast.js":{"bytes":5076,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast.js":{"bytes":3078,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-item.js":{"bytes":6009,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-icon.js":{"bytes":5416,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-content.js":{"bytes":1532,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-close-button.js":{"bytes":1339,"imports":[],"format":"esm"},"node_modules/wc-toast/src/index.js":{"bytes":364,"imports":[{"path":"node_modules/wc-toast/src/toast.js","kind":"import-statement","original":"./toast.js"},{"path":"node_modules/wc-toast/src/wc-toast.js","kind":"import-statement","original":"./wc-toast.js"},{"path":"node_modules/wc-toast/src/wc-toast-item.js","kind":"import-statement","original":"./wc-toast-item.js"},{"path":"node_modules/wc-toast/src/wc-toast-icon.js","kind":"import-statement","original":"./wc-toast-icon.js"},{"path":"node_modules/wc-toast/src/wc-toast-content.js","kind":"import-statement","original":"./wc-toast-content.js"},{"path":"node_modules/wc-toast/src/wc-toast-close-button.js","kind":"import-statement","original":"./wc-toast-close-button.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/utils.js":{"bytes":7268,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/environment.js":{"bytes":438,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/loop.js":{"bytes":875,"imports":[{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/globals.js":{"bytes":204,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js":{"bytes":1452,"imports":[{"path":"node_modules/svelte/src/runtime/internal/globals.js","kind":"import-statement","original":"./globals.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/dom.js":{"bytes":31031,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js","kind":"import-statement","original":"./ResizeObserverSingleton.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/style_manager.js":{"bytes":2945,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/animations.js":{"bytes":2499,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/style_manager.js","kind":"import-statement","original":"./style_manager.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/lifecycle.js":{"bytes":6139,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/scheduler.js":{"bytes":4292,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/transitions.js":{"bytes":9902,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/style_manager.js","kind":"import-statement","original":"./style_manager.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/await_block.js":{"bytes":2586,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/each.js":{"bytes":3367,"imports":[{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/spread.js":{"bytes":792,"imports":[],"format":"esm"},"node_modules/svelte/src/shared/boolean_attributes.js":{"bytes":684,"imports":[],"format":"esm"},"node_modules/svelte/src/shared/utils/names.js":{"bytes":2190,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/ssr.js":{"bytes":6518,"imports":[{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/shared/boolean_attributes.js","kind":"import-statement","original":"../../shared/boolean_attributes.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"},{"path":"node_modules/svelte/src/shared/utils/names.js","kind":"import-statement","original":"../../shared/utils/names.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/Component.js":{"bytes":14252,"imports":[{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"}],"format":"esm"},"node_modules/svelte/src/shared/version.js":{"bytes":247,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/dev.js":{"bytes":9263,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/Component.js","kind":"import-statement","original":"./Component.js"},{"path":"node_modules/svelte/src/shared/utils/names.js","kind":"import-statement","original":"../../shared/utils/names.js"},{"path":"node_modules/svelte/src/shared/version.js","kind":"import-statement","original":"../../shared/version.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/index.js":{"bytes":450,"imports":[{"path":"node_modules/svelte/src/runtime/internal/animations.js","kind":"import-statement","original":"./animations.js"},{"path":"node_modules/svelte/src/runtime/internal/await_block.js","kind":"import-statement","original":"./await_block.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/globals.js","kind":"import-statement","original":"./globals.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/spread.js","kind":"import-statement","original":"./spread.js"},{"path":"node_modules/svelte/src/runtime/internal/ssr.js","kind":"import-statement","original":"./ssr.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/Component.js","kind":"import-statement","original":"./Component.js"},{"path":"node_modules/svelte/src/runtime/internal/dev.js","kind":"import-statement","original":"./dev.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/disclose-version/index.js":{"bytes":194,"imports":[{"path":"node_modules/svelte/src/shared/version.js","kind":"import-statement","original":"../../../shared/version.js"}],"format":"esm"},"node_modules/svelte/src/runtime/index.js":{"bytes":239,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"./internal/index.js"}],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytes":848,"imports":[]},"node_modules/svelte-multiselect/dist/CircleSpinner.svelte":{"bytes":3556,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/svelte/src/runtime/easing/index.js":{"bytes":6218,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/transition/index.js":{"bytes":9591,"imports":[{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/animate/index.js":{"bytes":1399,"imports":[{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/store/index.js":{"bytes":5423,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/utils.js":{"bytes":148,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/motion/spring.js":{"bytes":4265,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"../store/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"},{"path":"node_modules/svelte/src/runtime/motion/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/tweened.js":{"bytes":3096,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"../store/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"},{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/motion/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/index.js":{"bytes":59,"imports":[{"path":"node_modules/svelte/src/runtime/motion/spring.js","kind":"import-statement","original":"./spring.js"},{"path":"node_modules/svelte/src/runtime/motion/tweened.js","kind":"import-statement","original":"./tweened.js"}],"format":"esm"},"node_modules/svelte-multiselect/dist/Wiggle.svelte":{"bytes":6291,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/motion/index.js","kind":"import-statement","original":"svelte/motion"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte":{"bytes":2553,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Cross.svelte":{"bytes":2529,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Disabled.svelte":{"bytes":2448,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Octocat.svelte":{"bytes":3547,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/index.js":{"bytes":235,"imports":[{"path":"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte","kind":"import-statement","original":"./ChevronExpand.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Cross.svelte","kind":"import-statement","original":"./Cross.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Disabled.svelte","kind":"import-statement","original":"./Disabled.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Octocat.svelte","kind":"import-statement","original":"./Octocat.svelte"}],"format":"esm"},"node_modules/svelte-multiselect/dist/utils.js":{"bytes":1032,"imports":[],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytes":10650,"imports":[]},"node_modules/svelte-multiselect/dist/MultiSelect.svelte":{"bytes":153564,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/animate/index.js","kind":"import-statement","original":"svelte/animate"},{"path":"node_modules/svelte-multiselect/dist/CircleSpinner.svelte","kind":"import-statement","original":"./CircleSpinner.svelte"},{"path":"node_modules/svelte-multiselect/dist/Wiggle.svelte","kind":"import-statement","original":"./Wiggle.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/index.js","kind":"import-statement","original":"./icons"},{"path":"node_modules/svelte-multiselect/dist/utils.js","kind":"import-statement","original":"./utils"},{"path":"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css"}],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytes":1052,"imports":[]},"node_modules/svelte-multiselect/dist/CmdPalette.svelte":{"bytes":15387,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/transition/index.js","kind":"import-statement","original":"svelte/transition"},{"path":"node_modules/svelte-multiselect/dist/MultiSelect.svelte","kind":"import-statement","original":"./MultiSelect.svelte"},{"path":"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/svelte-multiselect/dist/types.js":{"bytes":11,"imports":[],"format":"esm"},"node_modules/svelte-multiselect/dist/index.js":{"bytes":1362,"imports":[{"path":"node_modules/svelte-multiselect/dist/CircleSpinner.svelte","kind":"import-statement","original":"./CircleSpinner.svelte"},{"path":"node_modules/svelte-multiselect/dist/CmdPalette.svelte","kind":"import-statement","original":"./CmdPalette.svelte"},{"path":"node_modules/svelte-multiselect/dist/MultiSelect.svelte","kind":"import-statement","original":"./MultiSelect.svelte"},{"path":"node_modules/svelte-multiselect/dist/Wiggle.svelte","kind":"import-statement","original":"./Wiggle.svelte"},{"path":"node_modules/svelte-multiselect/dist/types.js","kind":"import-statement","original":"./types"}],"format":"esm"},"javascript/svelte/components/MultiSelectComponent.svelte":{"bytes":13274,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-multiselect/dist/index.js","kind":"import-statement","original":"svelte-multiselect"}],"format":"esm"},"javascript/svelte/components/notifications/PermissionButton.svelte":{"bytes":5441,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"}],"format":"esm"},"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte":{"bytes":2837,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte":{"bytes":2740,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte":{"bytes":2606,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/EmailSubscribe.svelte":{"bytes":16589,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte","kind":"import-statement","original":"./icons/EmailDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte","kind":"import-statement","original":"./icons/EmailEnabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"}],"format":"esm"},"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte":{"bytes":2941,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte":{"bytes":3363,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/IOSSubscribePush.svelte":{"bytes":35083,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte","kind":"import-statement","original":"./icons/PushDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte","kind":"import-statement","original":"./icons/PushEnabledIcon.svelte"}],"format":"esm"},"javascript/svelte/components/notifications/PwaSubscribePush.svelte":{"bytes":31584,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte","kind":"import-statement","original":"./icons/PushDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte","kind":"import-statement","original":"./icons/PushEnabledIcon.svelte"},{"path":"https://cdn.jsdelivr.net/npm/sweetalert2@10","kind":"dynamic-import","external":true}],"format":"esm"},"javascript/svelte/components/NotificationPermissions.svelte":{"bytes":37787,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"javascript/svelte/components/notifications/EmailSubscribe.svelte","kind":"import-statement","original":"./notifications/EmailSubscribe.svelte"},{"path":"javascript/svelte/components/notifications/IOSSubscribePush.svelte","kind":"import-statement","original":"./notifications/IOSSubscribePush.svelte"},{"path":"javascript/svelte/components/notifications/PwaSubscribePush.svelte","kind":"import-statement","original":"./notifications/PwaSubscribePush.svelte"}],"format":"esm"},"node_modules/libphonenumber-js/metadata.max.json.js":{"bytes":155145,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js":{"bytes":373,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/ParseError.js":{"bytes":5495,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/constants.js":{"bytes":1448,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/tools/semver-compare.js":{"bytes":930,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/isObject.js":{"bytes":215,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/metadata.js":{"bytes":21058,"imports":[{"path":"node_modules/libphonenumber-js/es6/tools/semver-compare.js","kind":"import-statement","original":"./tools/semver-compare.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js":{"bytes":5355,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js":{"bytes":4270,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./extension/createExtensionPattern.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js":{"bytes":1041,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./createExtensionPattern.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/parseDigits.js":{"bytes":4142,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js":{"bytes":4245,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getCountryCallingCode.js":{"bytes":174,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js":{"bytes":1851,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js":{"bytes":3616,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js","kind":"import-statement","original":"./mergeArrays.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isPossible.js":{"bytes":2960,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js":{"bytes":460,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getNumberType.js":{"bytes":4681,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isValid.js":{"bytes":2981,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./helpers/getNumberType.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js":{"bytes":1115,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js":{"bytes":2044,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js":{"bytes":1982,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js","kind":"import-statement","original":"./applyInternationalSeparatorStyle.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js":{"bytes":1172,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/RFC3966.js":{"bytes":3829,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js","kind":"import-statement","original":"./isViablePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/format.js":{"bytes":9771,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js","kind":"import-statement","original":"./helpers/getIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/RFC3966.js","kind":"import-statement","original":"./helpers/RFC3966.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/PhoneNumber.js":{"bytes":7398,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/isPossible.js","kind":"import-statement","original":"./isPossible.js"},{"path":"node_modules/libphonenumber-js/es6/isValid.js","kind":"import-statement","original":"./isValid.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./helpers/getNumberType.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js","kind":"import-statement","original":"./helpers/getPossibleCountriesForNumber.js"},{"path":"node_modules/libphonenumber-js/es6/format.js","kind":"import-statement","original":"./format.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js":{"bytes":1129,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js":{"bytes":5655,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js":{"bytes":5554,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js","kind":"import-statement","original":"./extractNationalNumberFromPossiblyIncompleteNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js":{"bytes":2295,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js","kind":"import-statement","original":"./extractNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./checkNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/getCountryCallingCode.js","kind":"import-statement","original":"../getCountryCallingCode.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js":{"bytes":6019,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js","kind":"import-statement","original":"./extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js":{"bytes":3224,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./getNumberType.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js":{"bytes":1037,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js","kind":"import-statement","original":"./getCountryByNationalNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js":{"bytes":3403,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js":{"bytes":2951,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js","kind":"import-statement","original":"./extractPhoneContext.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"../ParseError.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parse.js":{"bytes":12533,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js","kind":"import-statement","original":"./helpers/isViablePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js","kind":"import-statement","original":"./helpers/extension/extractExtension.js"},{"path":"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js","kind":"import-statement","original":"./parseIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/getCountryCallingCode.js","kind":"import-statement","original":"./getCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/isPossible.js","kind":"import-statement","original":"./isPossible.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js","kind":"import-statement","original":"./helpers/extractCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js","kind":"import-statement","original":"./helpers/extractNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./helpers/stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"./helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js","kind":"import-statement","original":"./helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js":{"bytes":1326,"imports":[{"path":"node_modules/libphonenumber-js/es6/parse.js","kind":"import-statement","original":"./parse.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/normalizeArguments.js":{"bytes":4258,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js":{"bytes":494,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js":{"bytes":1812,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumber.js":{"bytes":449,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isValidPhoneNumber.js":{"bytes":1665,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isPossiblePhoneNumber.js":{"bytes":1674,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/validatePhoneNumberLength.js":{"bytes":2231,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/LRUCache.js":{"bytes":3840,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/RegExpCache.js":{"bytes":1703,"imports":[{"path":"node_modules/libphonenumber-js/es6/findNumbers/LRUCache.js","kind":"import-statement","original":"./LRUCache.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/util.js":{"bytes":840,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js":{"bytes":9999,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/matchPhoneNumberStringAgainstPhoneNumber.js":{"bytes":2273,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"../parsePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/Leniency.js":{"bytes":16150,"imports":[{"path":"node_modules/libphonenumber-js/es6/isValid.js","kind":"import-statement","original":"../isValid.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"../helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/matchPhoneNumberStringAgainstPhoneNumber.js","kind":"import-statement","original":"./matchPhoneNumberStringAgainstPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"../helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/format.js","kind":"import-statement","original":"../format.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/parsePreCandidate.js":{"bytes":979,"imports":[{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/isValidPreCandidate.js":{"bytes":962,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/isValidCandidate.js":{"bytes":3059,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js","kind":"import-statement","original":"./utf-8.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js":{"bytes":16404,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./helpers/extension/createExtensionPattern.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/RegExpCache.js","kind":"import-statement","original":"./findNumbers/RegExpCache.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./findNumbers/util.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js","kind":"import-statement","original":"./findNumbers/utf-8.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/Leniency.js","kind":"import-statement","original":"./findNumbers/Leniency.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/parsePreCandidate.js","kind":"import-statement","original":"./findNumbers/parsePreCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/isValidPreCandidate.js","kind":"import-statement","original":"./findNumbers/isValidPreCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/isValidCandidate.js","kind":"import-statement","original":"./findNumbers/isValidCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"./parsePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/legacy/findNumbers.js":{"bytes":563,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"../normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/legacy/searchNumbers.js":{"bytes":1027,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"../normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../PhoneNumberMatcher.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findPhoneNumbersInText.js":{"bytes":1703,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"./PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/searchPhoneNumbersInText.js":{"bytes":1907,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"./PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeState.js":{"bytes":5074,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js":{"bytes":4749,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js":{"bytes":5904,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js":{"bytes":6416,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js":{"bytes":8676,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js","kind":"import-statement","original":"./AsYouTypeFormatter.PatternParser.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js":{"bytes":35950,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js","kind":"import-statement","original":"./AsYouTypeFormatter.util.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js","kind":"import-statement","original":"./AsYouTypeFormatter.complete.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js","kind":"import-statement","original":"./AsYouTypeFormatter.PatternMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js","kind":"import-statement","original":"./AsYouTypeFormatter.util.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js","kind":"import-statement","original":"./helpers/applyInternationalSeparatorStyle.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeParser.js":{"bytes":23261,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js","kind":"import-statement","original":"./helpers/extractCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js","kind":"import-statement","original":"./helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js","kind":"import-statement","original":"./helpers/extractNationalNumberFromPossiblyIncompleteNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./helpers/stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouType.js":{"bytes":21437,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeState.js","kind":"import-statement","original":"./AsYouTypeState.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js","kind":"import-statement","original":"./AsYouTypeFormatter.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeParser.js","kind":"import-statement","original":"./AsYouTypeParser.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"./helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js","kind":"import-statement","original":"./helpers/getCountryByNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getCountries.js":{"bytes":177,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getExampleNumber.js":{"bytes":259,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js":{"bytes":906,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouType.js","kind":"import-statement","original":"./AsYouType.js"}],"format":"esm"},"node_modules/libphonenumber-js/core/index.js":{"bytes":1952,"imports":[{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"../es6/ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js","kind":"import-statement","original":"../es6/parsePhoneNumberWithError.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"../es6/parsePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/isValidPhoneNumber.js","kind":"import-statement","original":"../es6/isValidPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/isPossiblePhoneNumber.js","kind":"import-statement","original":"../es6/isPossiblePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/validatePhoneNumberLength.js","kind":"import-statement","original":"../es6/validatePhoneNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/legacy/findNumbers.js","kind":"import-statement","original":"../es6/legacy/findNumbers.js"},{"path":"node_modules/libphonenumber-js/es6/legacy/searchNumbers.js","kind":"import-statement","original":"../es6/legacy/searchNumbers.js"},{"path":"node_modules/libphonenumber-js/es6/findPhoneNumbersInText.js","kind":"import-statement","original":"../es6/findPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/es6/searchPhoneNumbersInText.js","kind":"import-statement","original":"../es6/searchPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../es6/PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouType.js","kind":"import-statement","original":"../es6/AsYouType.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js","kind":"import-statement","original":"../es6/AsYouTypeFormatter.js"},{"path":"node_modules/libphonenumber-js/es6/getCountries.js","kind":"import-statement","original":"../es6/getCountries.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../es6/metadata.js"},{"path":"node_modules/libphonenumber-js/es6/getExampleNumber.js","kind":"import-statement","original":"../es6/getExampleNumber.js"},{"path":"node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js","kind":"import-statement","original":"../es6/formatIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js","kind":"import-statement","original":"../es6/parseIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"../es6/helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/RFC3966.js","kind":"import-statement","original":"../es6/helpers/RFC3966.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js":{"bytes":278,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/parsePhoneNumber.js":{"bytes":231,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isValidPhoneNumber.js":{"bytes":248,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isPossiblePhoneNumber.js":{"bytes":260,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/validatePhoneNumberLength.js":{"bytes":276,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/findNumbers.js":{"bytes":220,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/searchNumbers.js":{"bytes":228,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/findPhoneNumbersInText.js":{"bytes":264,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/searchPhoneNumbersInText.js":{"bytes":272,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/PhoneNumberMatcher.js":{"bytes":548,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/AsYouType.js":{"bytes":464,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isSupportedCountry.js":{"bytes":248,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getCountries.js":{"bytes":224,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js":{"bytes":260,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getExtPrefix.js":{"bytes":224,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/Metadata.js":{"bytes":440,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getExampleNumber.js":{"bytes":240,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/formatIncompletePhoneNumber.js":{"bytes":284,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/index.js":{"bytes":1794,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js","kind":"import-statement","original":"./exports/parsePhoneNumberWithError.js"},{"path":"node_modules/libphonenumber-js/max/exports/parsePhoneNumber.js","kind":"import-statement","original":"./exports/parsePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/isValidPhoneNumber.js","kind":"import-statement","original":"./exports/isValidPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/isPossiblePhoneNumber.js","kind":"import-statement","original":"./exports/isPossiblePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/validatePhoneNumberLength.js","kind":"import-statement","original":"./exports/validatePhoneNumberLength.js"},{"path":"node_modules/libphonenumber-js/max/exports/findNumbers.js","kind":"import-statement","original":"./exports/findNumbers.js"},{"path":"node_modules/libphonenumber-js/max/exports/searchNumbers.js","kind":"import-statement","original":"./exports/searchNumbers.js"},{"path":"node_modules/libphonenumber-js/max/exports/findPhoneNumbersInText.js","kind":"import-statement","original":"./exports/findPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/max/exports/searchPhoneNumbersInText.js","kind":"import-statement","original":"./exports/searchPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/max/exports/PhoneNumberMatcher.js","kind":"import-statement","original":"./exports/PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/max/exports/AsYouType.js","kind":"import-statement","original":"./exports/AsYouType.js"},{"path":"node_modules/libphonenumber-js/max/exports/isSupportedCountry.js","kind":"import-statement","original":"./exports/isSupportedCountry.js"},{"path":"node_modules/libphonenumber-js/max/exports/getCountries.js","kind":"import-statement","original":"./exports/getCountries.js"},{"path":"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js","kind":"import-statement","original":"./exports/getCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/max/exports/getExtPrefix.js","kind":"import-statement","original":"./exports/getExtPrefix.js"},{"path":"node_modules/libphonenumber-js/max/exports/Metadata.js","kind":"import-statement","original":"./exports/Metadata.js"},{"path":"node_modules/libphonenumber-js/max/exports/getExampleNumber.js","kind":"import-statement","original":"./exports/getExampleNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/formatIncompletePhoneNumber.js","kind":"import-statement","original":"./exports/formatIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../core/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/allCountry.js":{"bytes":11971,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js":{"bytes":4774,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/index.js":{"bytes":130,"imports":[{"path":"node_modules/svelte-tel-input/dist/assets/allCountry.js","kind":"import-statement","original":"./allCountry.js"},{"path":"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js","kind":"import-statement","original":"./examplePhoneNumbers.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/helpers.js":{"bytes":10562,"imports":[{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/assets/index.js","kind":"import-statement","original":"../assets/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/directives/clickOutsideAction.js":{"bytes":546,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js":{"bytes":843,"imports":[{"path":"node_modules/svelte-tel-input/dist/index.js","kind":"import-statement","original":"../../index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/index.js":{"bytes":130,"imports":[{"path":"node_modules/svelte-tel-input/dist/utils/helpers.js","kind":"import-statement","original":"./helpers.js"},{"path":"node_modules/svelte-tel-input/dist/utils/directives/clickOutsideAction.js","kind":"import-statement","original":"./directives/clickOutsideAction.js"},{"path":"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js","kind":"import-statement","original":"./directives/telInputAction.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/stores/index.js":{"bytes":391,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"svelte/store"}],"format":"esm"},"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte":{"bytes":21691,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/utils/index.js","kind":"import-statement","original":"../../utils/index.js"},{"path":"node_modules/svelte-tel-input/dist/stores/index.js","kind":"import-statement","original":"../../stores/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/index.js":{"bytes":381,"imports":[{"path":"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte","kind":"import-statement","original":"./components/input/TelInput.svelte"},{"path":"node_modules/svelte-tel-input/dist/utils/index.js","kind":"import-statement","original":"./utils/index.js"},{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/assets/index.js","kind":"import-statement","original":"./assets/index.js"}],"format":"esm"},"javascript/svelte/components/PhoneNumberPicker.svelte":{"bytes":23338,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-tel-input/dist/index.js","kind":"import-statement","original":"svelte-tel-input"}],"format":"esm"},"node_modules/uppload/dist/service.js":{"bytes":401,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/i18n.js":{"bytes":1304,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/elements.js":{"bytes":6896,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/assets.js":{"bytes":305,"imports":[],"format":"esm"},"node_modules/tabbable/index.js":{"bytes":4153,"imports":[],"format":"cjs"},"node_modules/xtend/immutable.js":{"bytes":384,"imports":[],"format":"cjs"},"node_modules/focus-trap/index.js":{"bytes":8562,"imports":[{"path":"node_modules/tabbable/index.js","kind":"require-call","original":"tabbable"},{"path":"node_modules/xtend/immutable.js","kind":"require-call","original":"xtend"}],"format":"cjs"},"node_modules/mitt/dist/mitt.es.js":{"bytes":2021,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/files.js":{"bytes":868,"imports":[],"format":"esm"},"node_modules/uppload/dist/uppload.js":{"bytes":29486,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"./service"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"./helpers/i18n"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./helpers/elements"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./helpers/assets"},{"path":"node_modules/focus-trap/index.js","kind":"import-statement","original":"focus-trap"},{"path":"node_modules/mitt/dist/mitt.es.js","kind":"import-statement","original":"mitt"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./helpers/files"}],"format":"esm"},"node_modules/uppload/dist/effect.js":{"bytes":367,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/http.js":{"bytes":1799,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/de.js":{"bytes":4939,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/en.js":{"bytes":5307,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/es.js":{"bytes":4813,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/fr.js":{"bytes":5065,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/hi.js":{"bytes":6608,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/it.js":{"bytes":4948,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/nl.js":{"bytes":4819,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/ru.js":{"bytes":5706,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/tr.js":{"bytes":4792,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/zh-TW.js":{"bytes":4674,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/index.js":{"bytes":308,"imports":[{"path":"node_modules/uppload/dist/i18n/de.js","kind":"import-statement","original":"./de"},{"path":"node_modules/uppload/dist/i18n/en.js","kind":"import-statement","original":"./en"},{"path":"node_modules/uppload/dist/i18n/es.js","kind":"import-statement","original":"./es"},{"path":"node_modules/uppload/dist/i18n/fr.js","kind":"import-statement","original":"./fr"},{"path":"node_modules/uppload/dist/i18n/hi.js","kind":"import-statement","original":"./hi"},{"path":"node_modules/uppload/dist/i18n/it.js","kind":"import-statement","original":"./it"},{"path":"node_modules/uppload/dist/i18n/nl.js","kind":"import-statement","original":"./nl"},{"path":"node_modules/uppload/dist/i18n/ru.js","kind":"import-statement","original":"./ru"},{"path":"node_modules/uppload/dist/i18n/tr.js","kind":"import-statement","original":"./tr"},{"path":"node_modules/uppload/dist/i18n/zh-TW.js","kind":"import-statement","original":"./zh-TW"}],"format":"esm"},"node_modules/uppload/dist/uploaders/xhr.js":{"bytes":2143,"imports":[],"format":"esm"},"node_modules/uppload/dist/services/camera.js":{"bytes":6932,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"../helpers/files"}],"format":"esm"},"node_modules/uppload/dist/helpers/microlink.js":{"bytes":5352,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"./http"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./assets"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./elements"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./files"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/instagram.js":{"bytes":2318,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/facebook.js":{"bytes":878,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/local.js":{"bytes":5660,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"../helpers/i18n"}],"format":"esm"},"node_modules/uppload/dist/helpers/search.js":{"bytes":5450,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"../helpers/http"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./assets"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./files"}],"format":"esm"},"node_modules/uppload/dist/services/search/giphy.js":{"bytes":1505,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/pixabay.js":{"bytes":1437,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/unsplash.js":{"bytes":1326,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/pexels.js":{"bytes":1506,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/url.js":{"bytes":683,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/screenshot.js":{"bytes":679,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/flickr.js":{"bytes":818,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/pinterest.js":{"bytes":997,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/deviantart.js":{"bytes":771,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/9gag.js":{"bytes":734,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/artstation.js":{"bytes":787,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/twitter.js":{"bytes":906,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/flipboard.js":{"bytes":817,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/fotki.js":{"bytes":1040,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/linkedin.js":{"bytes":1022,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/reddit.js":{"bytes":1098,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/tumblr.js":{"bytes":830,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/weheartit.js":{"bytes":835,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/cropperjs/dist/cropper.js":{"bytes":113993,"imports":[],"format":"cjs"},"node_modules/uppload/dist/effects/crop/index.js":{"bytes":4391,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/cropperjs/dist/cropper.js","kind":"import-statement","original":"cropperjs"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/rotate/index.js":{"bytes":3435,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/cropperjs/dist/cropper.js","kind":"import-statement","original":"cropperjs"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/flip/index.js":{"bytes":3584,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/preview/index.js":{"bytes":1233,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/helpers/filter.js":{"bytes":3502,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/brightness.js":{"bytes":660,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/blur.js":{"bytes":600,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/contrast.js":{"bytes":717,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/grayscale.js":{"bytes":1092,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/hue-rotate.js":{"bytes":807,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/invert.js":{"bytes":1193,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/sepia.js":{"bytes":743,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/saturate.js":{"bytes":1177,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/index.js":{"bytes":2397,"imports":[{"path":"node_modules/uppload/dist/uppload.js","kind":"import-statement","original":"./uppload"},{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"./service"},{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"./effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./helpers/elements"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"./helpers/http"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"./helpers/i18n"},{"path":"node_modules/uppload/dist/i18n/index.js","kind":"import-statement","original":"./i18n"},{"path":"node_modules/uppload/dist/uploaders/xhr.js","kind":"import-statement","original":"./uploaders/xhr"},{"path":"node_modules/uppload/dist/services/camera.js","kind":"import-statement","original":"./services/camera"},{"path":"node_modules/uppload/dist/services/microlink/instagram.js","kind":"import-statement","original":"./services/microlink/instagram"},{"path":"node_modules/uppload/dist/services/microlink/facebook.js","kind":"import-statement","original":"./services/microlink/facebook"},{"path":"node_modules/uppload/dist/services/local.js","kind":"import-statement","original":"./services/local"},{"path":"node_modules/uppload/dist/services/search/giphy.js","kind":"import-statement","original":"./services/search/giphy"},{"path":"node_modules/uppload/dist/services/search/pixabay.js","kind":"import-statement","original":"./services/search/pixabay"},{"path":"node_modules/uppload/dist/services/search/unsplash.js","kind":"import-statement","original":"./services/search/unsplash"},{"path":"node_modules/uppload/dist/services/search/pexels.js","kind":"import-statement","original":"./services/search/pexels"},{"path":"node_modules/uppload/dist/services/microlink/url.js","kind":"import-statement","original":"./services/microlink/url"},{"path":"node_modules/uppload/dist/services/microlink/screenshot.js","kind":"import-statement","original":"./services/microlink/screenshot"},{"path":"node_modules/uppload/dist/services/microlink/flickr.js","kind":"import-statement","original":"./services/microlink/flickr"},{"path":"node_modules/uppload/dist/services/microlink/pinterest.js","kind":"import-statement","original":"./services/microlink/pinterest"},{"path":"node_modules/uppload/dist/services/microlink/deviantart.js","kind":"import-statement","original":"./services/microlink/deviantart"},{"path":"node_modules/uppload/dist/services/microlink/9gag.js","kind":"import-statement","original":"./services/microlink/9gag"},{"path":"node_modules/uppload/dist/services/microlink/artstation.js","kind":"import-statement","original":"./services/microlink/artstation"},{"path":"node_modules/uppload/dist/services/microlink/twitter.js","kind":"import-statement","original":"./services/microlink/twitter"},{"path":"node_modules/uppload/dist/services/microlink/flipboard.js","kind":"import-statement","original":"./services/microlink/flipboard"},{"path":"node_modules/uppload/dist/services/microlink/fotki.js","kind":"import-statement","original":"./services/microlink/fotki"},{"path":"node_modules/uppload/dist/services/microlink/linkedin.js","kind":"import-statement","original":"./services/microlink/linkedin"},{"path":"node_modules/uppload/dist/services/microlink/reddit.js","kind":"import-statement","original":"./services/microlink/reddit"},{"path":"node_modules/uppload/dist/services/microlink/tumblr.js","kind":"import-statement","original":"./services/microlink/tumblr"},{"path":"node_modules/uppload/dist/services/microlink/weheartit.js","kind":"import-statement","original":"./services/microlink/weheartit"},{"path":"node_modules/uppload/dist/effects/crop/index.js","kind":"import-statement","original":"./effects/crop"},{"path":"node_modules/uppload/dist/effects/rotate/index.js","kind":"import-statement","original":"./effects/rotate"},{"path":"node_modules/uppload/dist/effects/flip/index.js","kind":"import-statement","original":"./effects/flip"},{"path":"node_modules/uppload/dist/effects/preview/index.js","kind":"import-statement","original":"./effects/preview"},{"path":"node_modules/uppload/dist/effects/filter/brightness.js","kind":"import-statement","original":"./effects/filter/brightness"},{"path":"node_modules/uppload/dist/effects/filter/blur.js","kind":"import-statement","original":"./effects/filter/blur"},{"path":"node_modules/uppload/dist/effects/filter/contrast.js","kind":"import-statement","original":"./effects/filter/contrast"},{"path":"node_modules/uppload/dist/effects/filter/grayscale.js","kind":"import-statement","original":"./effects/filter/grayscale"},{"path":"node_modules/uppload/dist/effects/filter/hue-rotate.js","kind":"import-statement","original":"./effects/filter/hue-rotate"},{"path":"node_modules/uppload/dist/effects/filter/invert.js","kind":"import-statement","original":"./effects/filter/invert"},{"path":"node_modules/uppload/dist/effects/filter/sepia.js","kind":"import-statement","original":"./effects/filter/sepia"},{"path":"node_modules/uppload/dist/effects/filter/saturate.js","kind":"import-statement","original":"./effects/filter/saturate"}],"format":"esm"},"node_modules/uppload/dist/themes/light.css":{"bytes":3479,"imports":[]},"node_modules/uppload/dist/uppload.css":{"bytes":27942,"imports":[]},"node_modules/uppload/dist/themes/dark.css":{"bytes":3410,"imports":[]},"fakecss:/Users/leoaudibert/Workspace/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytes":325,"imports":[{"path":"node_modules/uppload/dist/uppload.css","kind":"import-rule","original":"uppload/dist/uppload.css"},{"path":"node_modules/uppload/dist/themes/dark.css","kind":"import-rule","original":"uppload/dist/themes/dark.css"}]},"javascript/svelte/components/PhotoUploader.svelte":{"bytes":14562,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/uppload/dist/index.js","kind":"import-statement","original":"uppload"},{"path":"node_modules/uppload/dist/themes/light.css","kind":"import-statement","original":"uppload/dist/themes/light.css"},{"path":"node_modules/uppload/dist/uppload.css","kind":"import-statement","original":"uppload/dist/uppload.css"},{"path":"fakecss:/Users/leoaudibert/Workspace/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/@lit/reactive-element/css-tag.js":{"bytes":1589,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/reactive-element.js":{"bytes":5936,"imports":[{"path":"node_modules/@lit/reactive-element/css-tag.js","kind":"import-statement","original":"./css-tag.js"},{"path":"node_modules/@lit/reactive-element/css-tag.js","kind":"import-statement","original":"./css-tag.js"}],"format":"esm"},"node_modules/lit-html/lit-html.js":{"bytes":7258,"imports":[],"format":"esm"},"node_modules/lit-element/lit-element.js":{"bytes":1146,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"}],"format":"esm"},"node_modules/lit-html/is-server.js":{"bytes":162,"imports":[],"format":"esm"},"node_modules/lit/index.js":{"bytes":157,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"},{"path":"node_modules/lit-element/lit-element.js","kind":"import-statement","original":"lit-element/lit-element.js"},{"path":"node_modules/lit-html/is-server.js","kind":"import-statement","original":"lit-html/is-server.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/custom-element.js":{"bytes":272,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/property.js":{"bytes":1017,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"../reactive-element.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/state.js":{"bytes":238,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/property.js","kind":"import-statement","original":"./property.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/event-options.js":{"bytes":243,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/base.js":{"bytes":264,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query.js":{"bytes":539,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-all.js":{"bytes":319,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-async.js":{"bytes":311,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js":{"bytes":455,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js":{"bytes":392,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/lit/decorators.js":{"bytes":598,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/custom-element.js","kind":"import-statement","original":"@lit/reactive-element/decorators/custom-element.js"},{"path":"node_modules/@lit/reactive-element/decorators/property.js","kind":"import-statement","original":"@lit/reactive-element/decorators/property.js"},{"path":"node_modules/@lit/reactive-element/decorators/state.js","kind":"import-statement","original":"@lit/reactive-element/decorators/state.js"},{"path":"node_modules/@lit/reactive-element/decorators/event-options.js","kind":"import-statement","original":"@lit/reactive-element/decorators/event-options.js"},{"path":"node_modules/@lit/reactive-element/decorators/query.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-all.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-all.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-async.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-async.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-assigned-elements.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-assigned-nodes.js"}],"format":"esm"},"node_modules/lit-html/directive.js":{"bytes":481,"imports":[],"format":"esm"},"node_modules/lit-html/directives/class-map.js":{"bytes":1015,"imports":[{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"../lit-html.js"},{"path":"node_modules/lit-html/directive.js","kind":"import-statement","original":"../directive.js"}],"format":"esm"},"node_modules/lit/directives/class-map.js":{"bytes":85,"imports":[{"path":"node_modules/lit-html/directives/class-map.js","kind":"import-statement","original":"lit-html/directives/class-map.js"}],"format":"esm"},"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js":{"bytes":71181,"imports":[{"path":"node_modules/lit/index.js","kind":"import-statement","original":"lit"},{"path":"node_modules/lit/decorators.js","kind":"import-statement","original":"lit/decorators.js"},{"path":"node_modules/lit/directives/class-map.js","kind":"import-statement","original":"lit/directives/class-map.js"}],"format":"esm"},"javascript/svelte/components/PwaInstallButton.svelte":{"bytes":15996,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js","kind":"import-statement","original":"@khmyznikov/pwa-install"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"svelte/store"}],"format":"esm"},"javascript/svelte/components/SingleSelect.svelte":{"bytes":18651,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-multiselect/dist/index.js","kind":"import-statement","original":"svelte-multiselect"}],"format":"esm"},"javascript/svelte/components/ThemeToggle.svelte":{"bytes":6407,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"}],"format":"esm"},"javascript/svelte/main.js":{"bytes":2346,"imports":[{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/MultiSelectComponent.svelte","kind":"import-statement","original":"./components/MultiSelectComponent.svelte"},{"path":"javascript/svelte/components/NotificationPermissions.svelte","kind":"import-statement","original":"./components/NotificationPermissions.svelte"},{"path":"javascript/svelte/components/PhoneNumberPicker.svelte","kind":"import-statement","original":"./components/PhoneNumberPicker.svelte"},{"path":"javascript/svelte/components/PhotoUploader.svelte","kind":"import-statement","original":"./components/PhotoUploader.svelte"},{"path":"javascript/svelte/components/PwaInstallButton.svelte","kind":"import-statement","original":"./components/PwaInstallButton.svelte"},{"path":"javascript/svelte/components/SingleSelect.svelte","kind":"import-statement","original":"./components/SingleSelect.svelte"},{"path":"javascript/svelte/components/ThemeToggle.svelte","kind":"import-statement","original":"./components/ThemeToggle.svelte"},{"path":"javascript/svelte/components/notifications/PwaSubscribePush.svelte","kind":"import-statement","original":"./components/notifications/PwaSubscribePush.svelte"}],"format":"esm"}},"outputs":{"static/svelte_bundle.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1578308},"static/svelte_bundle.js":{"imports":[{"path":"https://cdn.jsdelivr.net/npm/sweetalert2@10","kind":"dynamic-import","external":true}],"exports":[],"entryPoint":"javascript/svelte/main.js","cssBundle":"static/svelte_bundle.css","inputs":{"node_modules/tabbable/index.js":{"bytesInOutput":1748},"node_modules/xtend/immutable.js":{"bytesInOutput":200},"node_modules/focus-trap/index.js":{"bytesInOutput":3599},"node_modules/cropperjs/dist/cropper.js":{"bytesInOutput":38742},"node_modules/wc-toast/src/toast.js":{"bytesInOutput":2026},"node_modules/wc-toast/src/wc-toast.js":{"bytesInOutput":2151},"node_modules/wc-toast/src/wc-toast-item.js":{"bytesInOutput":4367},"node_modules/wc-toast/src/wc-toast-icon.js":{"bytesInOutput":4567},"node_modules/wc-toast/src/wc-toast-content.js":{"bytesInOutput":813},"node_modules/wc-toast/src/wc-toast-close-button.js":{"bytesInOutput":1062},"node_modules/svelte/src/runtime/internal/utils.js":{"bytesInOutput":1349},"node_modules/svelte/src/runtime/internal/environment.js":{"bytesInOutput":111},"node_modules/svelte/src/runtime/internal/loop.js":{"bytesInOutput":214},"node_modules/svelte/src/runtime/internal/globals.js":{"bytesInOutput":72},"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js":{"bytesInOutput":467},"node_modules/svelte/src/runtime/internal/dom.js":{"bytesInOutput":2537},"node_modules/svelte/src/runtime/internal/style_manager.js":{"bytesInOutput":880},"node_modules/svelte/src/runtime/internal/animations.js":{"bytesInOutput":908},"node_modules/svelte/src/runtime/internal/index.js":{"bytesInOutput":0},"node_modules/svelte/src/runtime/internal/lifecycle.js":{"bytesInOutput":488},"node_modules/svelte/src/runtime/internal/scheduler.js":{"bytesInOutput":795},"node_modules/svelte/src/runtime/internal/transitions.js":{"bytesInOutput":263},"node_modules/svelte/src/runtime/internal/each.js":{"bytesInOutput":798},"node_modules/svelte/src/runtime/internal/spread.js":{"bytesInOutput":243},"node_modules/svelte/src/shared/boolean_attributes.js":{"bytesInOutput":307},"node_modules/svelte/src/runtime/internal/ssr.js":{"bytesInOutput":0},"node_modules/svelte/src/runtime/internal/Component.js":{"bytesInOutput":3989},"node_modules/svelte/src/shared/version.js":{"bytesInOutput":11},"node_modules/svelte/src/runtime/internal/disclose-version/index.js":{"bytesInOutput":78},"node_modules/svelte/src/runtime/index.js":{"bytesInOutput":0},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/CircleSpinner.svelte":{"bytesInOutput":740},"node_modules/svelte-multiselect/dist/index.js":{"bytesInOutput":359},"node_modules/svelte/src/runtime/easing/index.js":{"bytesInOutput":40},"node_modules/svelte/src/runtime/animate/index.js":{"bytesInOutput":490},"node_modules/svelte/src/runtime/store/index.js":{"bytesInOutput":379},"node_modules/svelte/src/runtime/motion/utils.js":{"bytesInOutput":74},"node_modules/svelte/src/runtime/motion/spring.js":{"bytesInOutput":1125},"node_modules/svelte/src/runtime/motion/index.js":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/Wiggle.svelte":{"bytesInOutput":1344},"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte":{"bytesInOutput":693},"node_modules/svelte-multiselect/dist/icons/index.js":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/icons/Cross.svelte":{"bytesInOutput":665},"node_modules/svelte-multiselect/dist/icons/Disabled.svelte":{"bytesInOutput":627},"node_modules/svelte-multiselect/dist/utils.js":{"bytesInOutput":528},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/MultiSelect.svelte":{"bytesInOutput":29159},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytesInOutput":0},"javascript/svelte/components/MultiSelectComponent.svelte":{"bytesInOutput":3125},"javascript/svelte/components/notifications/PermissionButton.svelte":{"bytesInOutput":1560},"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte":{"bytesInOutput":786},"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte":{"bytesInOutput":790},"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte":{"bytesInOutput":733},"javascript/svelte/components/notifications/EmailSubscribe.svelte":{"bytesInOutput":3593},"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte":{"bytesInOutput":837},"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte":{"bytesInOutput":1068},"javascript/svelte/components/notifications/IOSSubscribePush.svelte":{"bytesInOutput":5523},"javascript/svelte/components/notifications/PwaSubscribePush.svelte":{"bytesInOutput":5803},"javascript/svelte/components/NotificationPermissions.svelte":{"bytesInOutput":8915},"node_modules/libphonenumber-js/metadata.max.json.js":{"bytesInOutput":154007},"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js":{"bytesInOutput":87},"node_modules/libphonenumber-js/es6/ParseError.js":{"bytesInOutput":2764},"node_modules/libphonenumber-js/core/index.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/es6/constants.js":{"bytesInOutput":313},"node_modules/libphonenumber-js/es6/tools/semver-compare.js":{"bytesInOutput":310},"node_modules/libphonenumber-js/es6/helpers/isObject.js":{"bytesInOutput":72},"node_modules/libphonenumber-js/es6/metadata.js":{"bytesInOutput":8765},"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js":{"bytesInOutput":499},"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js":{"bytesInOutput":286},"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js":{"bytesInOutput":182},"node_modules/libphonenumber-js/es6/helpers/parseDigits.js":{"bytesInOutput":1420},"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js":{"bytesInOutput":1028},"node_modules/libphonenumber-js/es6/getCountryCallingCode.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js":{"bytesInOutput":972},"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js":{"bytesInOutput":460},"node_modules/libphonenumber-js/es6/isPossible.js":{"bytesInOutput":794},"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js":{"bytesInOutput":66},"node_modules/libphonenumber-js/es6/helpers/getNumberType.js":{"bytesInOutput":1510},"node_modules/libphonenumber-js/es6/isValid.js":{"bytesInOutput":219},"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js":{"bytesInOutput":246},"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js":{"bytesInOutput":80},"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js":{"bytesInOutput":301},"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js":{"bytesInOutput":209},"node_modules/libphonenumber-js/es6/helpers/RFC3966.js":{"bytesInOutput":190},"node_modules/libphonenumber-js/es6/format.js":{"bytesInOutput":3271},"node_modules/libphonenumber-js/es6/PhoneNumber.js":{"bytesInOutput":2752},"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js":{"bytesInOutput":262},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js":{"bytesInOutput":555},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js":{"bytesInOutput":403},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js":{"bytesInOutput":327},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js":{"bytesInOutput":561},"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js":{"bytesInOutput":1166},"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js":{"bytesInOutput":261},"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js":{"bytesInOutput":525},"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js":{"bytesInOutput":314},"node_modules/libphonenumber-js/es6/parse.js":{"bytesInOutput":2140},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js":{"bytesInOutput":753},"node_modules/libphonenumber-js/es6/normalizeArguments.js":{"bytesInOutput":2074},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js":{"bytesInOutput":85},"node_modules/libphonenumber-js/es6/AsYouTypeState.js":{"bytesInOutput":2033},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js":{"bytesInOutput":1350},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js":{"bytesInOutput":1232},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js":{"bytesInOutput":2978},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js":{"bytesInOutput":2840},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js":{"bytesInOutput":6866},"node_modules/libphonenumber-js/es6/AsYouTypeParser.js":{"bytesInOutput":5879},"node_modules/libphonenumber-js/es6/AsYouType.js":{"bytesInOutput":6283},"node_modules/libphonenumber-js/es6/getExampleNumber.js":{"bytesInOutput":51},"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js":{"bytesInOutput":38},"node_modules/libphonenumber-js/max/index.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/max/exports/AsYouType.js":{"bytesInOutput":113},"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js":{"bytesInOutput":38},"node_modules/libphonenumber-js/max/exports/Metadata.js":{"bytesInOutput":110},"node_modules/libphonenumber-js/max/exports/getExampleNumber.js":{"bytesInOutput":38},"node_modules/svelte-tel-input/dist/assets/allCountry.js":{"bytesInOutput":11098},"node_modules/svelte-tel-input/dist/assets/index.js":{"bytesInOutput":0},"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js":{"bytesInOutput":3581},"node_modules/svelte-tel-input/dist/utils/helpers.js":{"bytesInOutput":2112},"node_modules/svelte-tel-input/dist/utils/index.js":{"bytesInOutput":0},"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js":{"bytesInOutput":298},"node_modules/svelte-tel-input/dist/stores/index.js":{"bytesInOutput":94},"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte":{"bytesInOutput":3752},"node_modules/svelte-tel-input/dist/index.js":{"bytesInOutput":0},"javascript/svelte/components/PhoneNumberPicker.svelte":{"bytesInOutput":5386},"node_modules/uppload/dist/service.js":{"bytesInOutput":209},"node_modules/uppload/dist/helpers/i18n.js":{"bytesInOutput":389},"node_modules/uppload/dist/helpers/elements.js":{"bytesInOutput":2247},"node_modules/uppload/dist/helpers/assets.js":{"bytesInOutput":63},"node_modules/uppload/dist/uppload.js":{"bytesInOutput":13647},"node_modules/mitt/dist/mitt.es.js":{"bytesInOutput":271},"node_modules/uppload/dist/helpers/files.js":{"bytesInOutput":275},"node_modules/uppload/dist/index.js":{"bytesInOutput":0},"node_modules/uppload/dist/effect.js":{"bytesInOutput":191},"node_modules/uppload/dist/i18n/index.js":{"bytesInOutput":0},"node_modules/uppload/dist/i18n/en.js":{"bytesInOutput":2763},"node_modules/uppload/dist/services/local.js":{"bytesInOutput":3115},"node_modules/uppload/dist/effects/crop/index.js":{"bytesInOutput":2104},"node_modules/uppload/dist/effects/rotate/index.js":{"bytesInOutput":16},"node_modules/uppload/dist/themes/light.css":{"bytesInOutput":0},"node_modules/uppload/dist/uppload.css":{"bytesInOutput":0},"fakecss:/Users/leoaudibert/Workspace/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytesInOutput":0},"javascript/svelte/components/PhotoUploader.svelte":{"bytesInOutput":3059},"node_modules/@lit/reactive-element/css-tag.js":{"bytesInOutput":1338},"node_modules/@lit/reactive-element/reactive-element.js":{"bytesInOutput":5537},"node_modules/lit-html/lit-html.js":{"bytesInOutput":7082},"node_modules/lit-element/lit-element.js":{"bytesInOutput":759},"node_modules/lit/index.js":{"bytesInOutput":0},"node_modules/lit-html/is-server.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/custom-element.js":{"bytesInOutput":108},"node_modules/lit/decorators.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/property.js":{"bytesInOutput":762},"node_modules/@lit/reactive-element/decorators/state.js":{"bytesInOutput":55},"node_modules/@lit/reactive-element/decorators/event-options.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/base.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-all.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-async.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js":{"bytesInOutput":0},"node_modules/lit-html/directive.js":{"bytesInOutput":302},"node_modules/lit-html/directives/class-map.js":{"bytesInOutput":738},"node_modules/lit/directives/class-map.js":{"bytesInOutput":0},"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js":{"bytesInOutput":78896},"javascript/svelte/components/PwaInstallButton.svelte":{"bytesInOutput":3275},"javascript/svelte/components/SingleSelect.svelte":{"bytesInOutput":4217},"javascript/svelte/components/ThemeToggle.svelte":{"bytesInOutput":1445},"javascript/svelte/main.js":{"bytesInOutput":760}},"bytes":525029},"static/svelte_bundle.css.map":{"imports":[],"exports":[],"inputs":{},"bytes":63431},"static/svelte_bundle.css":{"imports":[],"inputs":{"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytesInOutput":246},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytesInOutput":4699},"fakecss:/Users/leoaudibert/Workspace/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytesInOutput":372},"node_modules/uppload/dist/themes/light.css":{"bytesInOutput":2170},"node_modules/uppload/dist/uppload.css":{"bytesInOutput":23982},"node_modules/uppload/dist/themes/dark.css":{"bytesInOutput":2938},"fakecss:/Users/leoaudibert/Workspace/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytesInOutput":0}},"bytes":34471}}} \ No newline at end of file +{"inputs":{"node_modules/wc-toast/src/toast.js":{"bytes":5076,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast.js":{"bytes":3078,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-item.js":{"bytes":6009,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-icon.js":{"bytes":5416,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-content.js":{"bytes":1532,"imports":[],"format":"esm"},"node_modules/wc-toast/src/wc-toast-close-button.js":{"bytes":1339,"imports":[],"format":"esm"},"node_modules/wc-toast/src/index.js":{"bytes":364,"imports":[{"path":"node_modules/wc-toast/src/toast.js","kind":"import-statement","original":"./toast.js"},{"path":"node_modules/wc-toast/src/wc-toast.js","kind":"import-statement","original":"./wc-toast.js"},{"path":"node_modules/wc-toast/src/wc-toast-item.js","kind":"import-statement","original":"./wc-toast-item.js"},{"path":"node_modules/wc-toast/src/wc-toast-icon.js","kind":"import-statement","original":"./wc-toast-icon.js"},{"path":"node_modules/wc-toast/src/wc-toast-content.js","kind":"import-statement","original":"./wc-toast-content.js"},{"path":"node_modules/wc-toast/src/wc-toast-close-button.js","kind":"import-statement","original":"./wc-toast-close-button.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/utils.js":{"bytes":7268,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/environment.js":{"bytes":438,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/loop.js":{"bytes":875,"imports":[{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/globals.js":{"bytes":204,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js":{"bytes":1452,"imports":[{"path":"node_modules/svelte/src/runtime/internal/globals.js","kind":"import-statement","original":"./globals.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/dom.js":{"bytes":31031,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js","kind":"import-statement","original":"./ResizeObserverSingleton.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/style_manager.js":{"bytes":2945,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/animations.js":{"bytes":2499,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/style_manager.js","kind":"import-statement","original":"./style_manager.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/lifecycle.js":{"bytes":6139,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/scheduler.js":{"bytes":4292,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/transitions.js":{"bytes":9902,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/style_manager.js","kind":"import-statement","original":"./style_manager.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/await_block.js":{"bytes":2586,"imports":[{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/each.js":{"bytes":3367,"imports":[{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/spread.js":{"bytes":792,"imports":[],"format":"esm"},"node_modules/svelte/src/shared/boolean_attributes.js":{"bytes":684,"imports":[],"format":"esm"},"node_modules/svelte/src/shared/utils/names.js":{"bytes":2190,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/ssr.js":{"bytes":6518,"imports":[{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/shared/boolean_attributes.js","kind":"import-statement","original":"../../shared/boolean_attributes.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"},{"path":"node_modules/svelte/src/shared/utils/names.js","kind":"import-statement","original":"../../shared/utils/names.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/Component.js":{"bytes":14252,"imports":[{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"}],"format":"esm"},"node_modules/svelte/src/shared/version.js":{"bytes":247,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/internal/dev.js":{"bytes":9263,"imports":[{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/Component.js","kind":"import-statement","original":"./Component.js"},{"path":"node_modules/svelte/src/shared/utils/names.js","kind":"import-statement","original":"../../shared/utils/names.js"},{"path":"node_modules/svelte/src/shared/version.js","kind":"import-statement","original":"../../shared/version.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/index.js":{"bytes":450,"imports":[{"path":"node_modules/svelte/src/runtime/internal/animations.js","kind":"import-statement","original":"./animations.js"},{"path":"node_modules/svelte/src/runtime/internal/await_block.js","kind":"import-statement","original":"./await_block.js"},{"path":"node_modules/svelte/src/runtime/internal/dom.js","kind":"import-statement","original":"./dom.js"},{"path":"node_modules/svelte/src/runtime/internal/environment.js","kind":"import-statement","original":"./environment.js"},{"path":"node_modules/svelte/src/runtime/internal/globals.js","kind":"import-statement","original":"./globals.js"},{"path":"node_modules/svelte/src/runtime/internal/each.js","kind":"import-statement","original":"./each.js"},{"path":"node_modules/svelte/src/runtime/internal/lifecycle.js","kind":"import-statement","original":"./lifecycle.js"},{"path":"node_modules/svelte/src/runtime/internal/loop.js","kind":"import-statement","original":"./loop.js"},{"path":"node_modules/svelte/src/runtime/internal/scheduler.js","kind":"import-statement","original":"./scheduler.js"},{"path":"node_modules/svelte/src/runtime/internal/spread.js","kind":"import-statement","original":"./spread.js"},{"path":"node_modules/svelte/src/runtime/internal/ssr.js","kind":"import-statement","original":"./ssr.js"},{"path":"node_modules/svelte/src/runtime/internal/transitions.js","kind":"import-statement","original":"./transitions.js"},{"path":"node_modules/svelte/src/runtime/internal/utils.js","kind":"import-statement","original":"./utils.js"},{"path":"node_modules/svelte/src/runtime/internal/Component.js","kind":"import-statement","original":"./Component.js"},{"path":"node_modules/svelte/src/runtime/internal/dev.js","kind":"import-statement","original":"./dev.js"}],"format":"esm"},"node_modules/svelte/src/runtime/internal/disclose-version/index.js":{"bytes":194,"imports":[{"path":"node_modules/svelte/src/shared/version.js","kind":"import-statement","original":"../../../shared/version.js"}],"format":"esm"},"node_modules/svelte/src/runtime/index.js":{"bytes":239,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"./internal/index.js"}],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytes":848,"imports":[]},"node_modules/svelte-multiselect/dist/CircleSpinner.svelte":{"bytes":3569,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/svelte/src/runtime/easing/index.js":{"bytes":6218,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/transition/index.js":{"bytes":9591,"imports":[{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/animate/index.js":{"bytes":1399,"imports":[{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/store/index.js":{"bytes":5423,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/utils.js":{"bytes":148,"imports":[],"format":"esm"},"node_modules/svelte/src/runtime/motion/spring.js":{"bytes":4265,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"../store/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"},{"path":"node_modules/svelte/src/runtime/motion/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/tweened.js":{"bytes":3096,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"../store/index.js"},{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"../internal/index.js"},{"path":"node_modules/svelte/src/runtime/easing/index.js","kind":"import-statement","original":"../easing/index.js"},{"path":"node_modules/svelte/src/runtime/motion/utils.js","kind":"import-statement","original":"./utils.js"}],"format":"esm"},"node_modules/svelte/src/runtime/motion/index.js":{"bytes":59,"imports":[{"path":"node_modules/svelte/src/runtime/motion/spring.js","kind":"import-statement","original":"./spring.js"},{"path":"node_modules/svelte/src/runtime/motion/tweened.js","kind":"import-statement","original":"./tweened.js"}],"format":"esm"},"node_modules/svelte-multiselect/dist/Wiggle.svelte":{"bytes":6291,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/motion/index.js","kind":"import-statement","original":"svelte/motion"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte":{"bytes":2553,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Cross.svelte":{"bytes":2529,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Disabled.svelte":{"bytes":2448,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/Octocat.svelte":{"bytes":3547,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"node_modules/svelte-multiselect/dist/icons/index.js":{"bytes":235,"imports":[{"path":"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte","kind":"import-statement","original":"./ChevronExpand.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Cross.svelte","kind":"import-statement","original":"./Cross.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Disabled.svelte","kind":"import-statement","original":"./Disabled.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/Octocat.svelte","kind":"import-statement","original":"./Octocat.svelte"}],"format":"esm"},"node_modules/svelte-multiselect/dist/utils.js":{"bytes":1032,"imports":[],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytes":10650,"imports":[]},"node_modules/svelte-multiselect/dist/MultiSelect.svelte":{"bytes":153577,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/animate/index.js","kind":"import-statement","original":"svelte/animate"},{"path":"node_modules/svelte-multiselect/dist/CircleSpinner.svelte","kind":"import-statement","original":"./CircleSpinner.svelte"},{"path":"node_modules/svelte-multiselect/dist/Wiggle.svelte","kind":"import-statement","original":"./Wiggle.svelte"},{"path":"node_modules/svelte-multiselect/dist/icons/index.js","kind":"import-statement","original":"./icons"},{"path":"node_modules/svelte-multiselect/dist/utils.js","kind":"import-statement","original":"./utils"},{"path":"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css"}],"format":"esm"},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytes":1052,"imports":[]},"node_modules/svelte-multiselect/dist/CmdPalette.svelte":{"bytes":15400,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/transition/index.js","kind":"import-statement","original":"svelte/transition"},{"path":"node_modules/svelte-multiselect/dist/MultiSelect.svelte","kind":"import-statement","original":"./MultiSelect.svelte"},{"path":"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/svelte-multiselect/dist/types.js":{"bytes":11,"imports":[],"format":"esm"},"node_modules/svelte-multiselect/dist/index.js":{"bytes":1362,"imports":[{"path":"node_modules/svelte-multiselect/dist/CircleSpinner.svelte","kind":"import-statement","original":"./CircleSpinner.svelte"},{"path":"node_modules/svelte-multiselect/dist/CmdPalette.svelte","kind":"import-statement","original":"./CmdPalette.svelte"},{"path":"node_modules/svelte-multiselect/dist/MultiSelect.svelte","kind":"import-statement","original":"./MultiSelect.svelte"},{"path":"node_modules/svelte-multiselect/dist/Wiggle.svelte","kind":"import-statement","original":"./Wiggle.svelte"},{"path":"node_modules/svelte-multiselect/dist/types.js","kind":"import-statement","original":"./types"}],"format":"esm"},"javascript/svelte/components/MultiSelectComponent.svelte":{"bytes":13274,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-multiselect/dist/index.js","kind":"import-statement","original":"svelte-multiselect"}],"format":"esm"},"javascript/svelte/components/notifications/PermissionButton.svelte":{"bytes":5441,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"}],"format":"esm"},"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte":{"bytes":2837,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte":{"bytes":2740,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte":{"bytes":2606,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/EmailSubscribe.svelte":{"bytes":16589,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte","kind":"import-statement","original":"./icons/EmailDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte","kind":"import-statement","original":"./icons/EmailEnabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"}],"format":"esm"},"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte":{"bytes":2941,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte":{"bytes":3363,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"}],"format":"esm"},"javascript/svelte/components/notifications/IOSSubscribePush.svelte":{"bytes":35083,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte","kind":"import-statement","original":"./icons/PushDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte","kind":"import-statement","original":"./icons/PushEnabledIcon.svelte"}],"format":"esm"},"javascript/svelte/components/notifications/PwaSubscribePush.svelte":{"bytes":31584,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/notifications/PermissionButton.svelte","kind":"import-statement","original":"./PermissionButton.svelte"},{"path":"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte","kind":"import-statement","original":"./icons/LoadingSpinner.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte","kind":"import-statement","original":"./icons/PushDisabledIcon.svelte"},{"path":"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte","kind":"import-statement","original":"./icons/PushEnabledIcon.svelte"},{"path":"https://cdn.jsdelivr.net/npm/sweetalert2@10","kind":"dynamic-import","external":true}],"format":"esm"},"javascript/svelte/components/NotificationPermissions.svelte":{"bytes":37787,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"javascript/svelte/components/notifications/EmailSubscribe.svelte","kind":"import-statement","original":"./notifications/EmailSubscribe.svelte"},{"path":"javascript/svelte/components/notifications/IOSSubscribePush.svelte","kind":"import-statement","original":"./notifications/IOSSubscribePush.svelte"},{"path":"javascript/svelte/components/notifications/PwaSubscribePush.svelte","kind":"import-statement","original":"./notifications/PwaSubscribePush.svelte"}],"format":"esm"},"node_modules/libphonenumber-js/metadata.max.json.js":{"bytes":155145,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js":{"bytes":373,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/ParseError.js":{"bytes":5495,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/constants.js":{"bytes":1448,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/tools/semver-compare.js":{"bytes":930,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/isObject.js":{"bytes":215,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/metadata.js":{"bytes":21058,"imports":[{"path":"node_modules/libphonenumber-js/es6/tools/semver-compare.js","kind":"import-statement","original":"./tools/semver-compare.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js":{"bytes":5355,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js":{"bytes":4270,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./extension/createExtensionPattern.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js":{"bytes":1041,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./createExtensionPattern.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/parseDigits.js":{"bytes":4142,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js":{"bytes":4245,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getCountryCallingCode.js":{"bytes":174,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js":{"bytes":1851,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js":{"bytes":3616,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js","kind":"import-statement","original":"./mergeArrays.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isPossible.js":{"bytes":2960,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js":{"bytes":460,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getNumberType.js":{"bytes":4681,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isValid.js":{"bytes":2981,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./helpers/getNumberType.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js":{"bytes":1115,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js":{"bytes":2044,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js":{"bytes":1982,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js","kind":"import-statement","original":"./applyInternationalSeparatorStyle.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js":{"bytes":1172,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/RFC3966.js":{"bytes":3829,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js","kind":"import-statement","original":"./isViablePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/format.js":{"bytes":9771,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js","kind":"import-statement","original":"./helpers/getIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/RFC3966.js","kind":"import-statement","original":"./helpers/RFC3966.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/PhoneNumber.js":{"bytes":7398,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/isPossible.js","kind":"import-statement","original":"./isPossible.js"},{"path":"node_modules/libphonenumber-js/es6/isValid.js","kind":"import-statement","original":"./isValid.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./helpers/getNumberType.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js","kind":"import-statement","original":"./helpers/getPossibleCountriesForNumber.js"},{"path":"node_modules/libphonenumber-js/es6/format.js","kind":"import-statement","original":"./format.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js":{"bytes":1129,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js":{"bytes":5655,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js":{"bytes":5554,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js","kind":"import-statement","original":"./extractNationalNumberFromPossiblyIncompleteNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js":{"bytes":2295,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js","kind":"import-statement","original":"./extractNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./checkNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/getCountryCallingCode.js","kind":"import-statement","original":"../getCountryCallingCode.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js":{"bytes":6019,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js","kind":"import-statement","original":"./extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js":{"bytes":3224,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getNumberType.js","kind":"import-statement","original":"./getNumberType.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js":{"bytes":1037,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js","kind":"import-statement","original":"./getCountryByNationalNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js":{"bytes":3403,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js":{"bytes":2951,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js","kind":"import-statement","original":"./extractPhoneContext.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"../ParseError.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parse.js":{"bytes":12533,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js","kind":"import-statement","original":"./helpers/isViablePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js","kind":"import-statement","original":"./helpers/extension/extractExtension.js"},{"path":"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js","kind":"import-statement","original":"./parseIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/getCountryCallingCode.js","kind":"import-statement","original":"./getCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/isPossible.js","kind":"import-statement","original":"./isPossible.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js","kind":"import-statement","original":"./helpers/matchesEntirely.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js","kind":"import-statement","original":"./helpers/extractCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js","kind":"import-statement","original":"./helpers/extractNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./helpers/stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"./helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js","kind":"import-statement","original":"./helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js":{"bytes":1326,"imports":[{"path":"node_modules/libphonenumber-js/es6/parse.js","kind":"import-statement","original":"./parse.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/normalizeArguments.js":{"bytes":4258,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js":{"bytes":494,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js":{"bytes":1812,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/parsePhoneNumber.js":{"bytes":449,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isValidPhoneNumber.js":{"bytes":1665,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/isPossiblePhoneNumber.js":{"bytes":1674,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber_.js","kind":"import-statement","original":"./parsePhoneNumber_.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/validatePhoneNumberLength.js":{"bytes":2231,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js","kind":"import-statement","original":"./parsePhoneNumberWithError_.js"},{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"./ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/LRUCache.js":{"bytes":3840,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/RegExpCache.js":{"bytes":1703,"imports":[{"path":"node_modules/libphonenumber-js/es6/findNumbers/LRUCache.js","kind":"import-statement","original":"./LRUCache.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/util.js":{"bytes":840,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js":{"bytes":9999,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/matchPhoneNumberStringAgainstPhoneNumber.js":{"bytes":2273,"imports":[{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"../parsePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/Leniency.js":{"bytes":16150,"imports":[{"path":"node_modules/libphonenumber-js/es6/isValid.js","kind":"import-statement","original":"../isValid.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"../helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/matchPhoneNumberStringAgainstPhoneNumber.js","kind":"import-statement","original":"./matchPhoneNumberStringAgainstPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../metadata.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"../helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/format.js","kind":"import-statement","original":"../format.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/parsePreCandidate.js":{"bytes":979,"imports":[{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/isValidPreCandidate.js":{"bytes":962,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/findNumbers/isValidCandidate.js":{"bytes":3059,"imports":[{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"../constants.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./util.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js","kind":"import-statement","original":"./utf-8.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js":{"bytes":16404,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js","kind":"import-statement","original":"./helpers/extension/createExtensionPattern.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/RegExpCache.js","kind":"import-statement","original":"./findNumbers/RegExpCache.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/util.js","kind":"import-statement","original":"./findNumbers/util.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/utf-8.js","kind":"import-statement","original":"./findNumbers/utf-8.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/Leniency.js","kind":"import-statement","original":"./findNumbers/Leniency.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/parsePreCandidate.js","kind":"import-statement","original":"./findNumbers/parsePreCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/isValidPreCandidate.js","kind":"import-statement","original":"./findNumbers/isValidPreCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/findNumbers/isValidCandidate.js","kind":"import-statement","original":"./findNumbers/isValidCandidate.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"./parsePhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/legacy/findNumbers.js":{"bytes":563,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"../normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/legacy/searchNumbers.js":{"bytes":1027,"imports":[{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"../normalizeArguments.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../PhoneNumberMatcher.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/findPhoneNumbersInText.js":{"bytes":1703,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"./PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/searchPhoneNumbersInText.js":{"bytes":1907,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"./PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/normalizeArguments.js","kind":"import-statement","original":"./normalizeArguments.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeState.js":{"bytes":5074,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js":{"bytes":4749,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js":{"bytes":5904,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js","kind":"import-statement","original":"./helpers/checkNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js":{"bytes":6416,"imports":[],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js":{"bytes":8676,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js","kind":"import-statement","original":"./AsYouTypeFormatter.PatternParser.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js":{"bytes":35950,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js","kind":"import-statement","original":"./AsYouTypeFormatter.util.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js","kind":"import-statement","original":"./AsYouTypeFormatter.complete.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js","kind":"import-statement","original":"./AsYouTypeFormatter.PatternMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js","kind":"import-statement","original":"./AsYouTypeFormatter.util.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js","kind":"import-statement","original":"./helpers/formatNationalNumberUsingFormat.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js","kind":"import-statement","original":"./helpers/applyInternationalSeparatorStyle.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouTypeParser.js":{"bytes":23261,"imports":[{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js","kind":"import-statement","original":"./helpers/extractCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js","kind":"import-statement","original":"./helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js","kind":"import-statement","original":"./helpers/extractNationalNumberFromPossiblyIncompleteNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js","kind":"import-statement","original":"./helpers/stripIddPrefix.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"./helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/constants.js","kind":"import-statement","original":"./constants.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/AsYouType.js":{"bytes":21437,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeState.js","kind":"import-statement","original":"./AsYouTypeState.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js","kind":"import-statement","original":"./AsYouTypeFormatter.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeParser.js","kind":"import-statement","original":"./AsYouTypeParser.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js","kind":"import-statement","original":"./helpers/getCountryByCallingCode.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js","kind":"import-statement","original":"./helpers/getCountryByNationalNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/isObject.js","kind":"import-statement","original":"./helpers/isObject.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getCountries.js":{"bytes":177,"imports":[{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"./metadata.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/getExampleNumber.js":{"bytes":259,"imports":[{"path":"node_modules/libphonenumber-js/es6/PhoneNumber.js","kind":"import-statement","original":"./PhoneNumber.js"}],"format":"esm"},"node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js":{"bytes":906,"imports":[{"path":"node_modules/libphonenumber-js/es6/AsYouType.js","kind":"import-statement","original":"./AsYouType.js"}],"format":"esm"},"node_modules/libphonenumber-js/core/index.js":{"bytes":1952,"imports":[{"path":"node_modules/libphonenumber-js/es6/ParseError.js","kind":"import-statement","original":"../es6/ParseError.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js","kind":"import-statement","original":"../es6/parsePhoneNumberWithError.js"},{"path":"node_modules/libphonenumber-js/es6/parsePhoneNumber.js","kind":"import-statement","original":"../es6/parsePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/isValidPhoneNumber.js","kind":"import-statement","original":"../es6/isValidPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/isPossiblePhoneNumber.js","kind":"import-statement","original":"../es6/isPossiblePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/validatePhoneNumberLength.js","kind":"import-statement","original":"../es6/validatePhoneNumberLength.js"},{"path":"node_modules/libphonenumber-js/es6/legacy/findNumbers.js","kind":"import-statement","original":"../es6/legacy/findNumbers.js"},{"path":"node_modules/libphonenumber-js/es6/legacy/searchNumbers.js","kind":"import-statement","original":"../es6/legacy/searchNumbers.js"},{"path":"node_modules/libphonenumber-js/es6/findPhoneNumbersInText.js","kind":"import-statement","original":"../es6/findPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/es6/searchPhoneNumbersInText.js","kind":"import-statement","original":"../es6/searchPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js","kind":"import-statement","original":"../es6/PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouType.js","kind":"import-statement","original":"../es6/AsYouType.js"},{"path":"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js","kind":"import-statement","original":"../es6/AsYouTypeFormatter.js"},{"path":"node_modules/libphonenumber-js/es6/getCountries.js","kind":"import-statement","original":"../es6/getCountries.js"},{"path":"node_modules/libphonenumber-js/es6/metadata.js","kind":"import-statement","original":"../es6/metadata.js"},{"path":"node_modules/libphonenumber-js/es6/getExampleNumber.js","kind":"import-statement","original":"../es6/getExampleNumber.js"},{"path":"node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js","kind":"import-statement","original":"../es6/formatIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js","kind":"import-statement","original":"../es6/parseIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/parseDigits.js","kind":"import-statement","original":"../es6/helpers/parseDigits.js"},{"path":"node_modules/libphonenumber-js/es6/helpers/RFC3966.js","kind":"import-statement","original":"../es6/helpers/RFC3966.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js":{"bytes":278,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/parsePhoneNumber.js":{"bytes":231,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isValidPhoneNumber.js":{"bytes":248,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isPossiblePhoneNumber.js":{"bytes":260,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/validatePhoneNumberLength.js":{"bytes":276,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/findNumbers.js":{"bytes":220,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/searchNumbers.js":{"bytes":228,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/findPhoneNumbersInText.js":{"bytes":264,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/searchPhoneNumbersInText.js":{"bytes":272,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/PhoneNumberMatcher.js":{"bytes":548,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/AsYouType.js":{"bytes":464,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/isSupportedCountry.js":{"bytes":248,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getCountries.js":{"bytes":224,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js":{"bytes":260,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getExtPrefix.js":{"bytes":224,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/Metadata.js":{"bytes":440,"imports":[{"path":"node_modules/libphonenumber-js/metadata.max.json.js","kind":"import-statement","original":"../../metadata.max.json.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/getExampleNumber.js":{"bytes":240,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/exports/formatIncompletePhoneNumber.js":{"bytes":284,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js","kind":"import-statement","original":"./withMetadataArgument.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../../core/index.js"}],"format":"esm"},"node_modules/libphonenumber-js/max/index.js":{"bytes":1794,"imports":[{"path":"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js","kind":"import-statement","original":"./exports/parsePhoneNumberWithError.js"},{"path":"node_modules/libphonenumber-js/max/exports/parsePhoneNumber.js","kind":"import-statement","original":"./exports/parsePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/isValidPhoneNumber.js","kind":"import-statement","original":"./exports/isValidPhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/isPossiblePhoneNumber.js","kind":"import-statement","original":"./exports/isPossiblePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/validatePhoneNumberLength.js","kind":"import-statement","original":"./exports/validatePhoneNumberLength.js"},{"path":"node_modules/libphonenumber-js/max/exports/findNumbers.js","kind":"import-statement","original":"./exports/findNumbers.js"},{"path":"node_modules/libphonenumber-js/max/exports/searchNumbers.js","kind":"import-statement","original":"./exports/searchNumbers.js"},{"path":"node_modules/libphonenumber-js/max/exports/findPhoneNumbersInText.js","kind":"import-statement","original":"./exports/findPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/max/exports/searchPhoneNumbersInText.js","kind":"import-statement","original":"./exports/searchPhoneNumbersInText.js"},{"path":"node_modules/libphonenumber-js/max/exports/PhoneNumberMatcher.js","kind":"import-statement","original":"./exports/PhoneNumberMatcher.js"},{"path":"node_modules/libphonenumber-js/max/exports/AsYouType.js","kind":"import-statement","original":"./exports/AsYouType.js"},{"path":"node_modules/libphonenumber-js/max/exports/isSupportedCountry.js","kind":"import-statement","original":"./exports/isSupportedCountry.js"},{"path":"node_modules/libphonenumber-js/max/exports/getCountries.js","kind":"import-statement","original":"./exports/getCountries.js"},{"path":"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js","kind":"import-statement","original":"./exports/getCountryCallingCode.js"},{"path":"node_modules/libphonenumber-js/max/exports/getExtPrefix.js","kind":"import-statement","original":"./exports/getExtPrefix.js"},{"path":"node_modules/libphonenumber-js/max/exports/Metadata.js","kind":"import-statement","original":"./exports/Metadata.js"},{"path":"node_modules/libphonenumber-js/max/exports/getExampleNumber.js","kind":"import-statement","original":"./exports/getExampleNumber.js"},{"path":"node_modules/libphonenumber-js/max/exports/formatIncompletePhoneNumber.js","kind":"import-statement","original":"./exports/formatIncompletePhoneNumber.js"},{"path":"node_modules/libphonenumber-js/core/index.js","kind":"import-statement","original":"../core/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/allCountry.js":{"bytes":11971,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js":{"bytes":4774,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/assets/index.js":{"bytes":130,"imports":[{"path":"node_modules/svelte-tel-input/dist/assets/allCountry.js","kind":"import-statement","original":"./allCountry.js"},{"path":"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js","kind":"import-statement","original":"./examplePhoneNumbers.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/helpers.js":{"bytes":10562,"imports":[{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/assets/index.js","kind":"import-statement","original":"../assets/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/directives/clickOutsideAction.js":{"bytes":546,"imports":[],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js":{"bytes":843,"imports":[{"path":"node_modules/svelte-tel-input/dist/index.js","kind":"import-statement","original":"../../index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/utils/index.js":{"bytes":130,"imports":[{"path":"node_modules/svelte-tel-input/dist/utils/helpers.js","kind":"import-statement","original":"./helpers.js"},{"path":"node_modules/svelte-tel-input/dist/utils/directives/clickOutsideAction.js","kind":"import-statement","original":"./directives/clickOutsideAction.js"},{"path":"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js","kind":"import-statement","original":"./directives/telInputAction.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/stores/index.js":{"bytes":391,"imports":[{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"svelte/store"}],"format":"esm"},"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte":{"bytes":21691,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/utils/index.js","kind":"import-statement","original":"../../utils/index.js"},{"path":"node_modules/svelte-tel-input/dist/stores/index.js","kind":"import-statement","original":"../../stores/index.js"}],"format":"esm"},"node_modules/svelte-tel-input/dist/index.js":{"bytes":381,"imports":[{"path":"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte","kind":"import-statement","original":"./components/input/TelInput.svelte"},{"path":"node_modules/svelte-tel-input/dist/utils/index.js","kind":"import-statement","original":"./utils/index.js"},{"path":"node_modules/libphonenumber-js/max/index.js","kind":"import-statement","original":"libphonenumber-js/max"},{"path":"node_modules/svelte-tel-input/dist/assets/index.js","kind":"import-statement","original":"./assets/index.js"}],"format":"esm"},"javascript/svelte/components/PhoneNumberPicker.svelte":{"bytes":23338,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-tel-input/dist/index.js","kind":"import-statement","original":"svelte-tel-input"}],"format":"esm"},"node_modules/uppload/dist/service.js":{"bytes":401,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/i18n.js":{"bytes":1304,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/elements.js":{"bytes":6896,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/assets.js":{"bytes":305,"imports":[],"format":"esm"},"node_modules/tabbable/index.js":{"bytes":4153,"imports":[],"format":"cjs"},"node_modules/xtend/immutable.js":{"bytes":384,"imports":[],"format":"cjs"},"node_modules/focus-trap/index.js":{"bytes":8562,"imports":[{"path":"node_modules/tabbable/index.js","kind":"require-call","original":"tabbable"},{"path":"node_modules/xtend/immutable.js","kind":"require-call","original":"xtend"}],"format":"cjs"},"node_modules/mitt/dist/mitt.es.js":{"bytes":2021,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/files.js":{"bytes":868,"imports":[],"format":"esm"},"node_modules/uppload/dist/uppload.js":{"bytes":29486,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"./service"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"./helpers/i18n"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./helpers/elements"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./helpers/assets"},{"path":"node_modules/focus-trap/index.js","kind":"import-statement","original":"focus-trap"},{"path":"node_modules/mitt/dist/mitt.es.js","kind":"import-statement","original":"mitt"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./helpers/files"}],"format":"esm"},"node_modules/uppload/dist/effect.js":{"bytes":367,"imports":[],"format":"esm"},"node_modules/uppload/dist/helpers/http.js":{"bytes":1799,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/de.js":{"bytes":4939,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/en.js":{"bytes":5307,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/es.js":{"bytes":4813,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/fr.js":{"bytes":5065,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/hi.js":{"bytes":6608,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/it.js":{"bytes":4948,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/nl.js":{"bytes":4819,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/ru.js":{"bytes":5706,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/tr.js":{"bytes":4792,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/zh-TW.js":{"bytes":4674,"imports":[],"format":"esm"},"node_modules/uppload/dist/i18n/index.js":{"bytes":308,"imports":[{"path":"node_modules/uppload/dist/i18n/de.js","kind":"import-statement","original":"./de"},{"path":"node_modules/uppload/dist/i18n/en.js","kind":"import-statement","original":"./en"},{"path":"node_modules/uppload/dist/i18n/es.js","kind":"import-statement","original":"./es"},{"path":"node_modules/uppload/dist/i18n/fr.js","kind":"import-statement","original":"./fr"},{"path":"node_modules/uppload/dist/i18n/hi.js","kind":"import-statement","original":"./hi"},{"path":"node_modules/uppload/dist/i18n/it.js","kind":"import-statement","original":"./it"},{"path":"node_modules/uppload/dist/i18n/nl.js","kind":"import-statement","original":"./nl"},{"path":"node_modules/uppload/dist/i18n/ru.js","kind":"import-statement","original":"./ru"},{"path":"node_modules/uppload/dist/i18n/tr.js","kind":"import-statement","original":"./tr"},{"path":"node_modules/uppload/dist/i18n/zh-TW.js","kind":"import-statement","original":"./zh-TW"}],"format":"esm"},"node_modules/uppload/dist/uploaders/xhr.js":{"bytes":2143,"imports":[],"format":"esm"},"node_modules/uppload/dist/services/camera.js":{"bytes":6932,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"../helpers/files"}],"format":"esm"},"node_modules/uppload/dist/helpers/microlink.js":{"bytes":5352,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"./http"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./assets"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./elements"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./files"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/instagram.js":{"bytes":2318,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/facebook.js":{"bytes":878,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/local.js":{"bytes":5660,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"../helpers/i18n"}],"format":"esm"},"node_modules/uppload/dist/helpers/search.js":{"bytes":5450,"imports":[{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"../service"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"../helpers/http"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"},{"path":"node_modules/uppload/dist/helpers/assets.js","kind":"import-statement","original":"./assets"},{"path":"node_modules/uppload/dist/helpers/files.js","kind":"import-statement","original":"./files"}],"format":"esm"},"node_modules/uppload/dist/services/search/giphy.js":{"bytes":1505,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/pixabay.js":{"bytes":1437,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/unsplash.js":{"bytes":1326,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/search/pexels.js":{"bytes":1506,"imports":[{"path":"node_modules/uppload/dist/helpers/search.js","kind":"import-statement","original":"../../helpers/search"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/url.js":{"bytes":683,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/screenshot.js":{"bytes":679,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/flickr.js":{"bytes":818,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/pinterest.js":{"bytes":997,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/deviantart.js":{"bytes":771,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/9gag.js":{"bytes":734,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/artstation.js":{"bytes":787,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/twitter.js":{"bytes":906,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/flipboard.js":{"bytes":817,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/fotki.js":{"bytes":1040,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/linkedin.js":{"bytes":1022,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/reddit.js":{"bytes":1098,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/tumblr.js":{"bytes":830,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/uppload/dist/services/microlink/weheartit.js":{"bytes":835,"imports":[{"path":"node_modules/uppload/dist/helpers/microlink.js","kind":"import-statement","original":"../../helpers/microlink"}],"format":"esm"},"node_modules/cropperjs/dist/cropper.js":{"bytes":113993,"imports":[],"format":"cjs"},"node_modules/uppload/dist/effects/crop/index.js":{"bytes":4391,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/cropperjs/dist/cropper.js","kind":"import-statement","original":"cropperjs"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/rotate/index.js":{"bytes":3435,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/cropperjs/dist/cropper.js","kind":"import-statement","original":"cropperjs"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/flip/index.js":{"bytes":3584,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/preview/index.js":{"bytes":1233,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/helpers/filter.js":{"bytes":3502,"imports":[{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"../effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"../helpers/elements"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/brightness.js":{"bytes":660,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/blur.js":{"bytes":600,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/contrast.js":{"bytes":717,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/grayscale.js":{"bytes":1092,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/hue-rotate.js":{"bytes":807,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/invert.js":{"bytes":1193,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/sepia.js":{"bytes":743,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/effects/filter/saturate.js":{"bytes":1177,"imports":[{"path":"node_modules/uppload/dist/helpers/filter.js","kind":"import-statement","original":"../../helpers/filter"}],"format":"esm"},"node_modules/uppload/dist/index.js":{"bytes":2397,"imports":[{"path":"node_modules/uppload/dist/uppload.js","kind":"import-statement","original":"./uppload"},{"path":"node_modules/uppload/dist/service.js","kind":"import-statement","original":"./service"},{"path":"node_modules/uppload/dist/effect.js","kind":"import-statement","original":"./effect"},{"path":"node_modules/uppload/dist/helpers/elements.js","kind":"import-statement","original":"./helpers/elements"},{"path":"node_modules/uppload/dist/helpers/http.js","kind":"import-statement","original":"./helpers/http"},{"path":"node_modules/uppload/dist/helpers/i18n.js","kind":"import-statement","original":"./helpers/i18n"},{"path":"node_modules/uppload/dist/i18n/index.js","kind":"import-statement","original":"./i18n"},{"path":"node_modules/uppload/dist/uploaders/xhr.js","kind":"import-statement","original":"./uploaders/xhr"},{"path":"node_modules/uppload/dist/services/camera.js","kind":"import-statement","original":"./services/camera"},{"path":"node_modules/uppload/dist/services/microlink/instagram.js","kind":"import-statement","original":"./services/microlink/instagram"},{"path":"node_modules/uppload/dist/services/microlink/facebook.js","kind":"import-statement","original":"./services/microlink/facebook"},{"path":"node_modules/uppload/dist/services/local.js","kind":"import-statement","original":"./services/local"},{"path":"node_modules/uppload/dist/services/search/giphy.js","kind":"import-statement","original":"./services/search/giphy"},{"path":"node_modules/uppload/dist/services/search/pixabay.js","kind":"import-statement","original":"./services/search/pixabay"},{"path":"node_modules/uppload/dist/services/search/unsplash.js","kind":"import-statement","original":"./services/search/unsplash"},{"path":"node_modules/uppload/dist/services/search/pexels.js","kind":"import-statement","original":"./services/search/pexels"},{"path":"node_modules/uppload/dist/services/microlink/url.js","kind":"import-statement","original":"./services/microlink/url"},{"path":"node_modules/uppload/dist/services/microlink/screenshot.js","kind":"import-statement","original":"./services/microlink/screenshot"},{"path":"node_modules/uppload/dist/services/microlink/flickr.js","kind":"import-statement","original":"./services/microlink/flickr"},{"path":"node_modules/uppload/dist/services/microlink/pinterest.js","kind":"import-statement","original":"./services/microlink/pinterest"},{"path":"node_modules/uppload/dist/services/microlink/deviantart.js","kind":"import-statement","original":"./services/microlink/deviantart"},{"path":"node_modules/uppload/dist/services/microlink/9gag.js","kind":"import-statement","original":"./services/microlink/9gag"},{"path":"node_modules/uppload/dist/services/microlink/artstation.js","kind":"import-statement","original":"./services/microlink/artstation"},{"path":"node_modules/uppload/dist/services/microlink/twitter.js","kind":"import-statement","original":"./services/microlink/twitter"},{"path":"node_modules/uppload/dist/services/microlink/flipboard.js","kind":"import-statement","original":"./services/microlink/flipboard"},{"path":"node_modules/uppload/dist/services/microlink/fotki.js","kind":"import-statement","original":"./services/microlink/fotki"},{"path":"node_modules/uppload/dist/services/microlink/linkedin.js","kind":"import-statement","original":"./services/microlink/linkedin"},{"path":"node_modules/uppload/dist/services/microlink/reddit.js","kind":"import-statement","original":"./services/microlink/reddit"},{"path":"node_modules/uppload/dist/services/microlink/tumblr.js","kind":"import-statement","original":"./services/microlink/tumblr"},{"path":"node_modules/uppload/dist/services/microlink/weheartit.js","kind":"import-statement","original":"./services/microlink/weheartit"},{"path":"node_modules/uppload/dist/effects/crop/index.js","kind":"import-statement","original":"./effects/crop"},{"path":"node_modules/uppload/dist/effects/rotate/index.js","kind":"import-statement","original":"./effects/rotate"},{"path":"node_modules/uppload/dist/effects/flip/index.js","kind":"import-statement","original":"./effects/flip"},{"path":"node_modules/uppload/dist/effects/preview/index.js","kind":"import-statement","original":"./effects/preview"},{"path":"node_modules/uppload/dist/effects/filter/brightness.js","kind":"import-statement","original":"./effects/filter/brightness"},{"path":"node_modules/uppload/dist/effects/filter/blur.js","kind":"import-statement","original":"./effects/filter/blur"},{"path":"node_modules/uppload/dist/effects/filter/contrast.js","kind":"import-statement","original":"./effects/filter/contrast"},{"path":"node_modules/uppload/dist/effects/filter/grayscale.js","kind":"import-statement","original":"./effects/filter/grayscale"},{"path":"node_modules/uppload/dist/effects/filter/hue-rotate.js","kind":"import-statement","original":"./effects/filter/hue-rotate"},{"path":"node_modules/uppload/dist/effects/filter/invert.js","kind":"import-statement","original":"./effects/filter/invert"},{"path":"node_modules/uppload/dist/effects/filter/sepia.js","kind":"import-statement","original":"./effects/filter/sepia"},{"path":"node_modules/uppload/dist/effects/filter/saturate.js","kind":"import-statement","original":"./effects/filter/saturate"}],"format":"esm"},"node_modules/uppload/dist/themes/light.css":{"bytes":3479,"imports":[]},"node_modules/uppload/dist/uppload.css":{"bytes":27942,"imports":[]},"node_modules/uppload/dist/themes/dark.css":{"bytes":3410,"imports":[]},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytes":325,"imports":[{"path":"node_modules/uppload/dist/uppload.css","kind":"import-rule","original":"uppload/dist/uppload.css"},{"path":"node_modules/uppload/dist/themes/dark.css","kind":"import-rule","original":"uppload/dist/themes/dark.css"}]},"javascript/svelte/components/PhotoUploader.svelte":{"bytes":14575,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/uppload/dist/index.js","kind":"import-statement","original":"uppload"},{"path":"node_modules/uppload/dist/themes/light.css","kind":"import-statement","original":"uppload/dist/themes/light.css"},{"path":"node_modules/uppload/dist/uppload.css","kind":"import-statement","original":"uppload/dist/uppload.css"},{"path":"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css","kind":"import-statement","original":"/Users/leoaudibert/Workspace/pagoda-based/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css"}],"format":"esm"},"node_modules/@lit/reactive-element/css-tag.js":{"bytes":1589,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/reactive-element.js":{"bytes":5936,"imports":[{"path":"node_modules/@lit/reactive-element/css-tag.js","kind":"import-statement","original":"./css-tag.js"},{"path":"node_modules/@lit/reactive-element/css-tag.js","kind":"import-statement","original":"./css-tag.js"}],"format":"esm"},"node_modules/lit-html/lit-html.js":{"bytes":7258,"imports":[],"format":"esm"},"node_modules/lit-element/lit-element.js":{"bytes":1146,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"}],"format":"esm"},"node_modules/lit-html/is-server.js":{"bytes":162,"imports":[],"format":"esm"},"node_modules/lit/index.js":{"bytes":157,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"@lit/reactive-element"},{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"lit-html"},{"path":"node_modules/lit-element/lit-element.js","kind":"import-statement","original":"lit-element/lit-element.js"},{"path":"node_modules/lit-html/is-server.js","kind":"import-statement","original":"lit-html/is-server.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/custom-element.js":{"bytes":272,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/property.js":{"bytes":1017,"imports":[{"path":"node_modules/@lit/reactive-element/reactive-element.js","kind":"import-statement","original":"../reactive-element.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/state.js":{"bytes":238,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/property.js","kind":"import-statement","original":"./property.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/event-options.js":{"bytes":243,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/base.js":{"bytes":264,"imports":[],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query.js":{"bytes":539,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-all.js":{"bytes":319,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-async.js":{"bytes":311,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js":{"bytes":455,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js":{"bytes":392,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/base.js","kind":"import-statement","original":"./base.js"}],"format":"esm"},"node_modules/lit/decorators.js":{"bytes":598,"imports":[{"path":"node_modules/@lit/reactive-element/decorators/custom-element.js","kind":"import-statement","original":"@lit/reactive-element/decorators/custom-element.js"},{"path":"node_modules/@lit/reactive-element/decorators/property.js","kind":"import-statement","original":"@lit/reactive-element/decorators/property.js"},{"path":"node_modules/@lit/reactive-element/decorators/state.js","kind":"import-statement","original":"@lit/reactive-element/decorators/state.js"},{"path":"node_modules/@lit/reactive-element/decorators/event-options.js","kind":"import-statement","original":"@lit/reactive-element/decorators/event-options.js"},{"path":"node_modules/@lit/reactive-element/decorators/query.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-all.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-all.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-async.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-async.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-assigned-elements.js"},{"path":"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js","kind":"import-statement","original":"@lit/reactive-element/decorators/query-assigned-nodes.js"}],"format":"esm"},"node_modules/lit-html/directive.js":{"bytes":481,"imports":[],"format":"esm"},"node_modules/lit-html/directives/class-map.js":{"bytes":1015,"imports":[{"path":"node_modules/lit-html/lit-html.js","kind":"import-statement","original":"../lit-html.js"},{"path":"node_modules/lit-html/directive.js","kind":"import-statement","original":"../directive.js"}],"format":"esm"},"node_modules/lit/directives/class-map.js":{"bytes":85,"imports":[{"path":"node_modules/lit-html/directives/class-map.js","kind":"import-statement","original":"lit-html/directives/class-map.js"}],"format":"esm"},"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js":{"bytes":71181,"imports":[{"path":"node_modules/lit/index.js","kind":"import-statement","original":"lit"},{"path":"node_modules/lit/decorators.js","kind":"import-statement","original":"lit/decorators.js"},{"path":"node_modules/lit/directives/class-map.js","kind":"import-statement","original":"lit/directives/class-map.js"}],"format":"esm"},"javascript/svelte/components/PwaInstallButton.svelte":{"bytes":15996,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js","kind":"import-statement","original":"@khmyznikov/pwa-install"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte/src/runtime/store/index.js","kind":"import-statement","original":"svelte/store"}],"format":"esm"},"javascript/svelte/components/SingleSelect.svelte":{"bytes":18651,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"},{"path":"node_modules/svelte-multiselect/dist/index.js","kind":"import-statement","original":"svelte-multiselect"}],"format":"esm"},"javascript/svelte/components/ThemeToggle.svelte":{"bytes":6407,"imports":[{"path":"node_modules/svelte/src/runtime/internal/index.js","kind":"import-statement","original":"svelte/internal"},{"path":"node_modules/svelte/src/runtime/internal/disclose-version/index.js","kind":"import-statement","original":"svelte/internal/disclose-version"},{"path":"node_modules/svelte/src/runtime/index.js","kind":"import-statement","original":"svelte"}],"format":"esm"},"javascript/svelte/main.js":{"bytes":2346,"imports":[{"path":"node_modules/wc-toast/src/index.js","kind":"import-statement","original":"wc-toast"},{"path":"javascript/svelte/components/MultiSelectComponent.svelte","kind":"import-statement","original":"./components/MultiSelectComponent.svelte"},{"path":"javascript/svelte/components/NotificationPermissions.svelte","kind":"import-statement","original":"./components/NotificationPermissions.svelte"},{"path":"javascript/svelte/components/PhoneNumberPicker.svelte","kind":"import-statement","original":"./components/PhoneNumberPicker.svelte"},{"path":"javascript/svelte/components/PhotoUploader.svelte","kind":"import-statement","original":"./components/PhotoUploader.svelte"},{"path":"javascript/svelte/components/PwaInstallButton.svelte","kind":"import-statement","original":"./components/PwaInstallButton.svelte"},{"path":"javascript/svelte/components/SingleSelect.svelte","kind":"import-statement","original":"./components/SingleSelect.svelte"},{"path":"javascript/svelte/components/ThemeToggle.svelte","kind":"import-statement","original":"./components/ThemeToggle.svelte"},{"path":"javascript/svelte/components/notifications/PwaSubscribePush.svelte","kind":"import-statement","original":"./components/notifications/PwaSubscribePush.svelte"}],"format":"esm"}},"outputs":{"static/svelte_bundle.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1578308},"static/svelte_bundle.js":{"imports":[{"path":"https://cdn.jsdelivr.net/npm/sweetalert2@10","kind":"dynamic-import","external":true}],"exports":[],"entryPoint":"javascript/svelte/main.js","cssBundle":"static/svelte_bundle.css","inputs":{"node_modules/tabbable/index.js":{"bytesInOutput":1748},"node_modules/xtend/immutable.js":{"bytesInOutput":200},"node_modules/focus-trap/index.js":{"bytesInOutput":3599},"node_modules/cropperjs/dist/cropper.js":{"bytesInOutput":38742},"node_modules/wc-toast/src/toast.js":{"bytesInOutput":2026},"node_modules/wc-toast/src/wc-toast.js":{"bytesInOutput":2151},"node_modules/wc-toast/src/wc-toast-item.js":{"bytesInOutput":4367},"node_modules/wc-toast/src/wc-toast-icon.js":{"bytesInOutput":4567},"node_modules/wc-toast/src/wc-toast-content.js":{"bytesInOutput":813},"node_modules/wc-toast/src/wc-toast-close-button.js":{"bytesInOutput":1062},"node_modules/svelte/src/runtime/internal/utils.js":{"bytesInOutput":1349},"node_modules/svelte/src/runtime/internal/environment.js":{"bytesInOutput":111},"node_modules/svelte/src/runtime/internal/loop.js":{"bytesInOutput":214},"node_modules/svelte/src/runtime/internal/globals.js":{"bytesInOutput":72},"node_modules/svelte/src/runtime/internal/ResizeObserverSingleton.js":{"bytesInOutput":467},"node_modules/svelte/src/runtime/internal/dom.js":{"bytesInOutput":2537},"node_modules/svelte/src/runtime/internal/style_manager.js":{"bytesInOutput":880},"node_modules/svelte/src/runtime/internal/animations.js":{"bytesInOutput":908},"node_modules/svelte/src/runtime/internal/index.js":{"bytesInOutput":0},"node_modules/svelte/src/runtime/internal/lifecycle.js":{"bytesInOutput":488},"node_modules/svelte/src/runtime/internal/scheduler.js":{"bytesInOutput":795},"node_modules/svelte/src/runtime/internal/transitions.js":{"bytesInOutput":263},"node_modules/svelte/src/runtime/internal/each.js":{"bytesInOutput":798},"node_modules/svelte/src/runtime/internal/spread.js":{"bytesInOutput":243},"node_modules/svelte/src/shared/boolean_attributes.js":{"bytesInOutput":307},"node_modules/svelte/src/runtime/internal/ssr.js":{"bytesInOutput":0},"node_modules/svelte/src/runtime/internal/Component.js":{"bytesInOutput":3989},"node_modules/svelte/src/shared/version.js":{"bytesInOutput":11},"node_modules/svelte/src/runtime/internal/disclose-version/index.js":{"bytesInOutput":78},"node_modules/svelte/src/runtime/index.js":{"bytesInOutput":0},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/CircleSpinner.svelte":{"bytesInOutput":740},"node_modules/svelte-multiselect/dist/index.js":{"bytesInOutput":359},"node_modules/svelte/src/runtime/easing/index.js":{"bytesInOutput":40},"node_modules/svelte/src/runtime/animate/index.js":{"bytesInOutput":490},"node_modules/svelte/src/runtime/store/index.js":{"bytesInOutput":379},"node_modules/svelte/src/runtime/motion/utils.js":{"bytesInOutput":74},"node_modules/svelte/src/runtime/motion/spring.js":{"bytesInOutput":1125},"node_modules/svelte/src/runtime/motion/index.js":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/Wiggle.svelte":{"bytesInOutput":1344},"node_modules/svelte-multiselect/dist/icons/ChevronExpand.svelte":{"bytesInOutput":693},"node_modules/svelte-multiselect/dist/icons/index.js":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/icons/Cross.svelte":{"bytesInOutput":665},"node_modules/svelte-multiselect/dist/icons/Disabled.svelte":{"bytesInOutput":627},"node_modules/svelte-multiselect/dist/utils.js":{"bytesInOutput":528},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytesInOutput":0},"node_modules/svelte-multiselect/dist/MultiSelect.svelte":{"bytesInOutput":29159},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytesInOutput":0},"javascript/svelte/components/MultiSelectComponent.svelte":{"bytesInOutput":3125},"javascript/svelte/components/notifications/PermissionButton.svelte":{"bytesInOutput":1560},"javascript/svelte/components/notifications/icons/EmailDisabledIcon.svelte":{"bytesInOutput":786},"javascript/svelte/components/notifications/icons/EmailEnabledIcon.svelte":{"bytesInOutput":790},"javascript/svelte/components/notifications/icons/LoadingSpinner.svelte":{"bytesInOutput":733},"javascript/svelte/components/notifications/EmailSubscribe.svelte":{"bytesInOutput":3593},"javascript/svelte/components/notifications/icons/PushDisabledIcon.svelte":{"bytesInOutput":837},"javascript/svelte/components/notifications/icons/PushEnabledIcon.svelte":{"bytesInOutput":1068},"javascript/svelte/components/notifications/IOSSubscribePush.svelte":{"bytesInOutput":5523},"javascript/svelte/components/notifications/PwaSubscribePush.svelte":{"bytesInOutput":5803},"javascript/svelte/components/NotificationPermissions.svelte":{"bytesInOutput":8915},"node_modules/libphonenumber-js/metadata.max.json.js":{"bytesInOutput":154007},"node_modules/libphonenumber-js/max/exports/withMetadataArgument.js":{"bytesInOutput":87},"node_modules/libphonenumber-js/es6/ParseError.js":{"bytesInOutput":2764},"node_modules/libphonenumber-js/core/index.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/es6/constants.js":{"bytesInOutput":313},"node_modules/libphonenumber-js/es6/tools/semver-compare.js":{"bytesInOutput":310},"node_modules/libphonenumber-js/es6/helpers/isObject.js":{"bytesInOutput":72},"node_modules/libphonenumber-js/es6/metadata.js":{"bytesInOutput":8765},"node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js":{"bytesInOutput":499},"node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js":{"bytesInOutput":286},"node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js":{"bytesInOutput":182},"node_modules/libphonenumber-js/es6/helpers/parseDigits.js":{"bytesInOutput":1420},"node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js":{"bytesInOutput":1028},"node_modules/libphonenumber-js/es6/getCountryCallingCode.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/es6/helpers/mergeArrays.js":{"bytesInOutput":972},"node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js":{"bytesInOutput":460},"node_modules/libphonenumber-js/es6/isPossible.js":{"bytesInOutput":794},"node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js":{"bytesInOutput":66},"node_modules/libphonenumber-js/es6/helpers/getNumberType.js":{"bytesInOutput":1510},"node_modules/libphonenumber-js/es6/isValid.js":{"bytesInOutput":219},"node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js":{"bytesInOutput":246},"node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js":{"bytesInOutput":80},"node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js":{"bytesInOutput":301},"node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js":{"bytesInOutput":209},"node_modules/libphonenumber-js/es6/helpers/RFC3966.js":{"bytesInOutput":190},"node_modules/libphonenumber-js/es6/format.js":{"bytesInOutput":3271},"node_modules/libphonenumber-js/es6/PhoneNumber.js":{"bytesInOutput":2752},"node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js":{"bytesInOutput":262},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js":{"bytesInOutput":555},"node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js":{"bytesInOutput":403},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js":{"bytesInOutput":327},"node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js":{"bytesInOutput":561},"node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js":{"bytesInOutput":1166},"node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js":{"bytesInOutput":261},"node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js":{"bytesInOutput":525},"node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js":{"bytesInOutput":314},"node_modules/libphonenumber-js/es6/parse.js":{"bytesInOutput":2140},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js":{"bytesInOutput":753},"node_modules/libphonenumber-js/es6/normalizeArguments.js":{"bytesInOutput":2074},"node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js":{"bytesInOutput":85},"node_modules/libphonenumber-js/es6/AsYouTypeState.js":{"bytesInOutput":2033},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js":{"bytesInOutput":1350},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js":{"bytesInOutput":1232},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js":{"bytesInOutput":2978},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js":{"bytesInOutput":2840},"node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js":{"bytesInOutput":6866},"node_modules/libphonenumber-js/es6/AsYouTypeParser.js":{"bytesInOutput":5879},"node_modules/libphonenumber-js/es6/AsYouType.js":{"bytesInOutput":6283},"node_modules/libphonenumber-js/es6/getExampleNumber.js":{"bytesInOutput":51},"node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js":{"bytesInOutput":38},"node_modules/libphonenumber-js/max/index.js":{"bytesInOutput":0},"node_modules/libphonenumber-js/max/exports/AsYouType.js":{"bytesInOutput":113},"node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js":{"bytesInOutput":38},"node_modules/libphonenumber-js/max/exports/Metadata.js":{"bytesInOutput":110},"node_modules/libphonenumber-js/max/exports/getExampleNumber.js":{"bytesInOutput":38},"node_modules/svelte-tel-input/dist/assets/allCountry.js":{"bytesInOutput":11098},"node_modules/svelte-tel-input/dist/assets/index.js":{"bytesInOutput":0},"node_modules/svelte-tel-input/dist/assets/examplePhoneNumbers.js":{"bytesInOutput":3581},"node_modules/svelte-tel-input/dist/utils/helpers.js":{"bytesInOutput":2112},"node_modules/svelte-tel-input/dist/utils/index.js":{"bytesInOutput":0},"node_modules/svelte-tel-input/dist/utils/directives/telInputAction.js":{"bytesInOutput":298},"node_modules/svelte-tel-input/dist/stores/index.js":{"bytesInOutput":94},"node_modules/svelte-tel-input/dist/components/input/TelInput.svelte":{"bytesInOutput":3752},"node_modules/svelte-tel-input/dist/index.js":{"bytesInOutput":0},"javascript/svelte/components/PhoneNumberPicker.svelte":{"bytesInOutput":5386},"node_modules/uppload/dist/service.js":{"bytesInOutput":209},"node_modules/uppload/dist/helpers/i18n.js":{"bytesInOutput":389},"node_modules/uppload/dist/helpers/elements.js":{"bytesInOutput":2247},"node_modules/uppload/dist/helpers/assets.js":{"bytesInOutput":63},"node_modules/uppload/dist/uppload.js":{"bytesInOutput":13647},"node_modules/mitt/dist/mitt.es.js":{"bytesInOutput":271},"node_modules/uppload/dist/helpers/files.js":{"bytesInOutput":275},"node_modules/uppload/dist/index.js":{"bytesInOutput":0},"node_modules/uppload/dist/effect.js":{"bytesInOutput":191},"node_modules/uppload/dist/i18n/index.js":{"bytesInOutput":0},"node_modules/uppload/dist/i18n/en.js":{"bytesInOutput":2763},"node_modules/uppload/dist/services/local.js":{"bytesInOutput":3115},"node_modules/uppload/dist/effects/crop/index.js":{"bytesInOutput":2104},"node_modules/uppload/dist/effects/rotate/index.js":{"bytesInOutput":16},"node_modules/uppload/dist/themes/light.css":{"bytesInOutput":0},"node_modules/uppload/dist/uppload.css":{"bytesInOutput":0},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytesInOutput":0},"javascript/svelte/components/PhotoUploader.svelte":{"bytesInOutput":3059},"node_modules/@lit/reactive-element/css-tag.js":{"bytesInOutput":1338},"node_modules/@lit/reactive-element/reactive-element.js":{"bytesInOutput":5537},"node_modules/lit-html/lit-html.js":{"bytesInOutput":7082},"node_modules/lit-element/lit-element.js":{"bytesInOutput":759},"node_modules/lit/index.js":{"bytesInOutput":0},"node_modules/lit-html/is-server.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/custom-element.js":{"bytesInOutput":108},"node_modules/lit/decorators.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/property.js":{"bytesInOutput":762},"node_modules/@lit/reactive-element/decorators/state.js":{"bytesInOutput":55},"node_modules/@lit/reactive-element/decorators/event-options.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/base.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-all.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-async.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-assigned-elements.js":{"bytesInOutput":0},"node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js":{"bytesInOutput":0},"node_modules/lit-html/directive.js":{"bytesInOutput":302},"node_modules/lit-html/directives/class-map.js":{"bytesInOutput":738},"node_modules/lit/directives/class-map.js":{"bytesInOutput":0},"node_modules/@khmyznikov/pwa-install/dist/es/pwa-install.es.js":{"bytesInOutput":78896},"javascript/svelte/components/PwaInstallButton.svelte":{"bytesInOutput":3275},"javascript/svelte/components/SingleSelect.svelte":{"bytesInOutput":4217},"javascript/svelte/components/ThemeToggle.svelte":{"bytesInOutput":1445},"javascript/svelte/main.js":{"bytesInOutput":760}},"bytes":525029},"static/svelte_bundle.css.map":{"imports":[],"exports":[],"inputs":{},"bytes":63431},"static/svelte_bundle.css":{"imports":[],"inputs":{"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CircleSpinner.esbuild-svelte-fake-css":{"bytesInOutput":246},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/MultiSelect.esbuild-svelte-fake-css":{"bytesInOutput":4699},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/node_modules/svelte-multiselect/dist/CmdPalette.esbuild-svelte-fake-css":{"bytesInOutput":372},"node_modules/uppload/dist/themes/light.css":{"bytesInOutput":2170},"node_modules/uppload/dist/uppload.css":{"bytesInOutput":23982},"node_modules/uppload/dist/themes/dark.css":{"bytesInOutput":2938},"fakecss:/Users/leoaudibert/Workspace/pagoda-based/goship/javascript/svelte/components/PhotoUploader.esbuild-svelte-fake-css":{"bytesInOutput":0}},"bytes":34471}}} \ No newline at end of file From 50fcdc378efe4031ffee5276c12f9aaf33e5e830 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 17:16:17 -0800 Subject: [PATCH 010/291] refactor: split ship CLI module and reorganize numbered docs --- Makefile | 9 +- Procfile.dev | 1 - Procfile.worker | 1 + cli/ship/cli.go | 266 +++++++++++++++ cli/ship/cli_test.go | 245 ++++++++++++++ cli/ship/cmd/ship/main.go | 11 + cli/ship/go.mod | 4 + docs/00-index.md | 63 ++++ docs/README.md | 39 --- .../01-architecture.md} | 0 .../02-structure-and-boundaries.md} | 0 .../03-project-scope-analysis.md} | 0 .../04-http-routes.md} | 0 .../05-data-model.md} | 0 .../06-known-gaps-and-risks.md} | 0 .../01-ai-agent-guide.md} | 8 +- .../02-development-workflows.md} | 0 docs/policies/01-engineering-standards.md | 89 +++++ docs/reference/01-cli.md | 81 +++++ .../roadmap/01-framework-plan.md | 42 ++- go.work | 6 + go.work.sum | 314 ++++++++++++++++++ 22 files changed, 1129 insertions(+), 50 deletions(-) create mode 100644 Procfile.worker create mode 100644 cli/ship/cli.go create mode 100644 cli/ship/cli_test.go create mode 100644 cli/ship/cmd/ship/main.go create mode 100644 cli/ship/go.mod create mode 100644 docs/00-index.md delete mode 100644 docs/README.md rename docs/{architecture.md => architecture/01-architecture.md} (100%) rename docs/{structure-and-boundaries.md => architecture/02-structure-and-boundaries.md} (100%) rename docs/{project-scope-analysis.md => architecture/03-project-scope-analysis.md} (100%) rename docs/{http-routes.md => architecture/04-http-routes.md} (100%) rename docs/{data-model.md => architecture/05-data-model.md} (100%) rename docs/{known-gaps-and-risks.md => architecture/06-known-gaps-and-risks.md} (100%) rename docs/{ai-agent-guide.md => guides/01-ai-agent-guide.md} (86%) rename docs/{development-workflows.md => guides/02-development-workflows.md} (100%) create mode 100644 docs/policies/01-engineering-standards.md create mode 100644 docs/reference/01-cli.md rename README-framework-plan.md => docs/roadmap/01-framework-plan.md (94%) create mode 100644 go.work create mode 100644 go.work.sum diff --git a/Makefile b/Makefile index cdaaae01..2c98ec27 100644 --- a/Makefile +++ b/Makefile @@ -44,11 +44,16 @@ hooks: ## Install git hooks via lefthook # Core workflow ------------------------------------------------------------------------------ .PHONY: dev -dev: ## Start local development (infra + Go processes; no JS/CSS build/watch) +dev: ## Start local development (infra + web server only) bash scripts/dev.sh "$(DCO_BIN)" +.PHONY: dev-worker +dev-worker: ## Start local development worker only (infra + worker process) + bash scripts/up.sh "$(DCO_BIN)" + overmind start -f Procfile.worker + .PHONY: dev-full -dev-full: ## Start local development including JS/CSS watchers +dev-full: ## Start local development including web, worker, and JS/CSS watchers bash scripts/up.sh "$(DCO_BIN)" echo "Tip: run 'nvm use v18.20.7' if JS tooling fails." overmind start diff --git a/Procfile.dev b/Procfile.dev index 651d246e..1a1739b6 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,2 +1 @@ watch-go: make watch-go -watch-go-worker: make worker diff --git a/Procfile.worker b/Procfile.worker new file mode 100644 index 00000000..c250636e --- /dev/null +++ b/Procfile.worker @@ -0,0 +1 @@ +watch-go-worker: make worker diff --git a/cli/ship/cli.go b/cli/ship/cli.go new file mode 100644 index 00000000..1fe13044 --- /dev/null +++ b/cli/ship/cli.go @@ -0,0 +1,266 @@ +package ship + +import ( + "errors" + "flag" + "fmt" + "io" + "os" + "os/exec" + "strconv" +) + +const ( + atlasDir = "file://ent/migrate/migrations" + atlasURL = "postgres://admin:admin@localhost:5432/app?search_path=public&sslmode=disable" +) + +type CmdRunner interface { + Run(name string, args ...string) (int, error) +} + +type ExecRunner struct{} + +func (ExecRunner) Run(name string, args ...string) (int, error) { + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + if err := cmd.Run(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return exitErr.ExitCode(), nil + } + return 1, err + } + return 0, nil +} + +type CLI struct { + Out io.Writer + Err io.Writer + Runner CmdRunner +} + +func New() CLI { + return CLI{ + Out: os.Stdout, + Err: os.Stderr, + Runner: ExecRunner{}, + } +} + +// Run executes the ship CLI. +func Run(args []string) int { + return New().Run(args) +} + +func (c CLI) Run(args []string) int { + if len(args) == 0 { + printRootHelp(c.Out) + return 0 + } + + switch args[0] { + case "help", "-h", "--help": + printRootHelp(c.Out) + return 0 + case "dev", "shipdev": + return c.runDev(args[1:]) + case "test": + return c.runTest(args[1:]) + case "db": + return c.runDB(args[1:]) + default: + fmt.Fprintf(c.Err, "unknown command: %s\n\n", args[0]) + printRootHelp(c.Err) + return 1 + } +} + +func (c CLI) runDev(args []string) int { + for _, arg := range args { + if arg == "-h" || arg == "--help" || arg == "help" { + printDevHelp(c.Out) + return 0 + } + } + + mode := "web" + if len(args) > 0 { + switch args[0] { + case "worker": + mode = "worker" + args = args[1:] + case "all": + mode = "all" + args = args[1:] + } + } + + fs := flag.NewFlagSet("dev", flag.ContinueOnError) + fs.SetOutput(io.Discard) + worker := fs.Bool("worker", false, "run worker-only dev mode") + all := fs.Bool("all", false, "run full dev mode") + if err := fs.Parse(args); err != nil { + fmt.Fprintf(c.Err, "invalid dev arguments: %v\n", err) + return 1 + } + if fs.NArg() > 0 { + fmt.Fprintf(c.Err, "unexpected dev arguments: %v\n", fs.Args()) + return 1 + } + if *worker && *all { + fmt.Fprintln(c.Err, "cannot set both --worker and --all") + return 1 + } + if *worker { + mode = "worker" + } + if *all { + mode = "all" + } + + switch mode { + case "web": + return c.runCmd("make", "dev") + case "worker": + return c.runCmd("make", "dev-worker") + case "all": + return c.runCmd("make", "dev-full") + default: + fmt.Fprintf(c.Err, "unknown dev mode: %s\n", mode) + return 1 + } +} + +func (c CLI) runTest(args []string) int { + for _, arg := range args { + if arg == "-h" || arg == "--help" || arg == "help" { + printTestHelp(c.Out) + return 0 + } + } + + fs := flag.NewFlagSet("test", flag.ContinueOnError) + fs.SetOutput(io.Discard) + integration := fs.Bool("integration", false, "run integration tests") + if err := fs.Parse(args); err != nil { + fmt.Fprintf(c.Err, "invalid test arguments: %v\n", err) + return 1 + } + if fs.NArg() > 0 { + fmt.Fprintf(c.Err, "unexpected test arguments: %v\n", fs.Args()) + return 1 + } + + if *integration { + return c.runCmd("make", "test-integration") + } + return c.runCmd("make", "test") +} + +func (c CLI) runDB(args []string) int { + if len(args) == 0 { + printDBHelp(c.Err) + return 1 + } + + switch args[0] { + case "create": + if len(args) != 1 { + fmt.Fprintln(c.Err, "usage: ship db create") + return 1 + } + // In current local setup, creating DB is equivalent to bringing infra up. + return c.runCmd("make", "up") + case "migrate": + if len(args) != 1 { + fmt.Fprintln(c.Err, "usage: ship db migrate") + return 1 + } + return c.runCmd("make", "migrate") + case "rollback": + return c.runDBRollback(args[1:]) + case "seed": + if len(args) != 1 { + fmt.Fprintln(c.Err, "usage: ship db seed") + return 1 + } + return c.runCmd("make", "seed") + case "help", "-h", "--help": + printDBHelp(c.Out) + return 0 + default: + fmt.Fprintf(c.Err, "unknown db command: %s\n\n", args[0]) + printDBHelp(c.Err) + return 1 + } +} + +func (c CLI) runDBRollback(args []string) int { + amount := "1" + if len(args) > 1 { + fmt.Fprintln(c.Err, "usage: ship db rollback [amount]") + return 1 + } + if len(args) == 1 { + if _, err := strconv.Atoi(args[0]); err != nil { + fmt.Fprintf(c.Err, "invalid rollback amount %q: must be an integer\n", args[0]) + return 1 + } + amount = args[0] + } + + return c.runCmd("atlas", "migrate", "down", "--dir", atlasDir, "--url", atlasURL, amount) +} + +func (c CLI) runCmd(name string, args ...string) int { + code, err := c.Runner.Run(name, args...) + if err != nil { + fmt.Fprintf(c.Err, "failed to run command %q: %v\n", append([]string{name}, args...), err) + return 1 + } + return code +} + +func printRootHelp(w io.Writer) { + fmt.Fprintln(w, "ship - GoShip CLI") + fmt.Fprintln(w) + fmt.Fprintln(w, "Usage:") + fmt.Fprintln(w, " ship dev [worker|all] [--worker|--all]") + fmt.Fprintln(w, " ship test [--integration]") + fmt.Fprintln(w, " ship db ") + fmt.Fprintln(w) + fmt.Fprintln(w, "Examples:") + fmt.Fprintln(w, " ship dev") + fmt.Fprintln(w, " ship dev worker") + fmt.Fprintln(w, " ship dev --all") + fmt.Fprintln(w, " ship test --integration") + fmt.Fprintln(w, " ship db migrate") + fmt.Fprintln(w, " ship db rollback 1") +} + +func printDevHelp(w io.Writer) { + fmt.Fprintln(w, "ship dev commands:") + fmt.Fprintln(w, " ship dev") + fmt.Fprintln(w, " ship dev worker") + fmt.Fprintln(w, " ship dev all") + fmt.Fprintln(w, " ship dev --worker") + fmt.Fprintln(w, " ship dev --all") +} + +func printDBHelp(w io.Writer) { + fmt.Fprintln(w, "ship db commands:") + fmt.Fprintln(w, " ship db create") + fmt.Fprintln(w, " ship db migrate") + fmt.Fprintln(w, " ship db rollback [amount]") + fmt.Fprintln(w, " ship db seed") +} + +func printTestHelp(w io.Writer) { + fmt.Fprintln(w, "ship test commands:") + fmt.Fprintln(w, " ship test") + fmt.Fprintln(w, " ship test --integration") +} diff --git a/cli/ship/cli_test.go b/cli/ship/cli_test.go new file mode 100644 index 00000000..7e7066fa --- /dev/null +++ b/cli/ship/cli_test.go @@ -0,0 +1,245 @@ +package ship + +import ( + "bytes" + "errors" + "strings" + "testing" +) + +type fakeCall struct { + name string + args []string +} + +type fakeRunner struct { + calls []fakeCall + code int + err error + nextCode map[string]int + nextErr map[string]error +} + +func (f *fakeRunner) Run(name string, args ...string) (int, error) { + f.calls = append(f.calls, fakeCall{name: name, args: args}) + key := name + " " + strings.Join(args, " ") + if err, ok := f.nextErr[key]; ok { + return 1, err + } + if code, ok := f.nextCode[key]; ok { + return code, nil + } + return f.code, f.err +} + +func TestRun_DispatchAndArgs(t *testing.T) { + tests := []struct { + name string + args []string + wantCode int + wantCalls []fakeCall + wantOut string + wantErr string + runnerCode int + runnerErr error + }{ + { + name: "no args prints root help", + args: nil, + wantCode: 0, + wantOut: "ship - GoShip CLI", + wantCalls: nil, + }, + { + name: "unknown command", + args: []string{"wat"}, + wantCode: 1, + wantErr: "unknown command: wat", + wantCalls: nil, + }, + { + name: "dev default", + args: []string{"dev"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev"}}}, + }, + { + name: "shipdev alias", + args: []string{"shipdev"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev"}}}, + }, + { + name: "dev worker positional", + args: []string{"dev", "worker"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev-worker"}}}, + }, + { + name: "dev all positional", + args: []string{"dev", "all"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev-full"}}}, + }, + { + name: "dev worker flag", + args: []string{"dev", "--worker"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev-worker"}}}, + }, + { + name: "dev all flag", + args: []string{"dev", "--all"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"dev-full"}}}, + }, + { + name: "dev both flags invalid", + args: []string{"dev", "--all", "--worker"}, + wantCode: 1, + wantErr: "cannot set both --worker and --all", + }, + { + name: "dev unexpected arg invalid", + args: []string{"dev", "worker", "extra"}, + wantCode: 1, + wantErr: "unexpected dev arguments", + }, + { + name: "dev help", + args: []string{"dev", "--help"}, + wantCode: 0, + wantOut: "ship dev commands:", + }, + { + name: "test default", + args: []string{"test"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"test"}}}, + }, + { + name: "test integration", + args: []string{"test", "--integration"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"test-integration"}}}, + }, + { + name: "test invalid arg", + args: []string{"test", "extra"}, + wantCode: 1, + wantErr: "unexpected test arguments", + }, + { + name: "test help", + args: []string{"test", "--help"}, + wantCode: 0, + wantOut: "ship test commands:", + }, + { + name: "db create", + args: []string{"db", "create"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"up"}}}, + }, + { + name: "db migrate", + args: []string{"db", "migrate"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"migrate"}}}, + }, + { + name: "db seed", + args: []string{"db", "seed"}, + wantCode: 0, + wantCalls: []fakeCall{{name: "make", args: []string{"seed"}}}, + }, + { + name: "db rollback default amount", + args: []string{"db", "rollback"}, + wantCode: 0, + wantCalls: []fakeCall{{ + name: "atlas", + args: []string{"migrate", "down", "--dir", atlasDir, "--url", atlasURL, "1"}, + }}, + }, + { + name: "db rollback explicit amount", + args: []string{"db", "rollback", "3"}, + wantCode: 0, + wantCalls: []fakeCall{{ + name: "atlas", + args: []string{"migrate", "down", "--dir", atlasDir, "--url", atlasURL, "3"}, + }}, + }, + { + name: "db rollback invalid amount", + args: []string{"db", "rollback", "x"}, + wantCode: 1, + wantErr: "invalid rollback amount", + }, + { + name: "db rollback too many args", + args: []string{"db", "rollback", "1", "2"}, + wantCode: 1, + wantErr: "usage: ship db rollback [amount]", + }, + { + name: "db missing subcommand", + args: []string{"db"}, + wantCode: 1, + wantErr: "ship db commands:", + }, + { + name: "db help", + args: []string{"db", "help"}, + wantCode: 0, + wantOut: "ship db commands:", + }, + { + name: "runner exit code is propagated", + args: []string{"dev"}, + wantCode: 7, + wantCalls: []fakeCall{{name: "make", args: []string{"dev"}}}, + runnerCode: 7, + }, + { + name: "runner error prints message", + args: []string{"dev"}, + wantCode: 1, + wantCalls: []fakeCall{{name: "make", args: []string{"dev"}}}, + wantErr: "failed to run command", + runnerErr: errors.New("boom"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := &bytes.Buffer{} + errOut := &bytes.Buffer{} + runner := &fakeRunner{code: tt.runnerCode, err: tt.runnerErr} + cli := CLI{Out: out, Err: errOut, Runner: runner} + + got := cli.Run(tt.args) + if got != tt.wantCode { + t.Fatalf("exit code = %d, want %d", got, tt.wantCode) + } + if tt.wantOut != "" && !strings.Contains(out.String(), tt.wantOut) { + t.Fatalf("stdout = %q, want contains %q", out.String(), tt.wantOut) + } + if tt.wantErr != "" && !strings.Contains(errOut.String(), tt.wantErr) { + t.Fatalf("stderr = %q, want contains %q", errOut.String(), tt.wantErr) + } + if len(runner.calls) != len(tt.wantCalls) { + t.Fatalf("calls len = %d, want %d", len(runner.calls), len(tt.wantCalls)) + } + for i := range tt.wantCalls { + if runner.calls[i].name != tt.wantCalls[i].name { + t.Fatalf("call[%d] name = %q, want %q", i, runner.calls[i].name, tt.wantCalls[i].name) + } + if strings.Join(runner.calls[i].args, " ") != strings.Join(tt.wantCalls[i].args, " ") { + t.Fatalf("call[%d] args = %v, want %v", i, runner.calls[i].args, tt.wantCalls[i].args) + } + } + }) + } +} diff --git a/cli/ship/cmd/ship/main.go b/cli/ship/cmd/ship/main.go new file mode 100644 index 00000000..ce785f9c --- /dev/null +++ b/cli/ship/cmd/ship/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + shipcli "github.com/mikestefanello/pagoda/cli/ship" +) + +func main() { + os.Exit(shipcli.Run(os.Args[1:])) +} diff --git a/cli/ship/go.mod b/cli/ship/go.mod new file mode 100644 index 00000000..3d1d512d --- /dev/null +++ b/cli/ship/go.mod @@ -0,0 +1,4 @@ +module github.com/mikestefanello/pagoda/cli/ship + +go 1.23.0 + diff --git a/docs/00-index.md b/docs/00-index.md new file mode 100644 index 00000000..583c058b --- /dev/null +++ b/docs/00-index.md @@ -0,0 +1,63 @@ +# Documentation Index + +This `docs/` directory is internal and implementation-focused. +It is not intended as user-facing product documentation. + +## Goals + +- Explain what the project does today based on code, not assumptions. +- Give developers and AI agents a fast map of where to make changes. +- Capture system risks and incomplete areas so work is directed intentionally. + +## README Index + +1. `README-01` - `../README.md`: project-level overview and onboarding. +2. `README-02` - `00-index.md`: documentation hub and map of all internal docs. + +## Structure + +### Architecture + +1. `A01` - `architecture/01-architecture.md`: runtime architecture, request flow, and service composition. +2. `A02` - `architecture/02-structure-and-boundaries.md`: canonical placement rules for app vs framework code. +3. `A03` - `architecture/03-project-scope-analysis.md`: end-to-end feature and capability analysis. +4. `A04` - `architecture/04-http-routes.md`: route inventory grouped by access level and purpose. +5. `A05` - `architecture/05-data-model.md`: Ent entities and domain model coverage. +6. `A06` - `architecture/06-known-gaps-and-risks.md`: confirmed implementation gaps and technical risks. + +### Guides + +1. `G01` - `guides/01-ai-agent-guide.md`: practical guide for AI agents working in this repo. +2. `G02` - `guides/02-development-workflows.md`: day-to-day run/build/test/migration workflows. + +### Reference + +1. `R01` - `reference/01-cli.md`: living CLI specification (`ship`) for developers and agents. + +### Policies + +1. `P01` - `policies/01-engineering-standards.md`: baseline requirements for maintainable repositories (hooks, CI, tests, docs, versioning). + +### Roadmap + +1. `M01` - `roadmap/01-framework-plan.md`: long-term framework strategy and execution tracker. + +## Primary Source Files Used For This Analysis + +- `cmd/web/main.go` +- `cmd/worker/main.go` +- `cmd/seed/main.go` +- `cli/ship/cmd/ship/main.go` +- `cli/ship/cli.go` +- `pkg/services/container.go` +- `app/goship/web/routes/router.go` +- `app/goship/web/routes/*.go` +- `pkg/tasks/*.go` +- `pkg/repos/**/*.go` +- `ent/schema/*.go` +- `config/config.go` +- `config/config.yaml` +- `Makefile` +- `build.mjs` +- `package.json` +- `e2e_tests/tests/goship.spec.ts` diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 67bca1ef..00000000 --- a/docs/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Internal Documentation (Developers and AI Agents) - -This `docs/` directory is internal and implementation-focused. -It is not intended as user-facing product documentation. - -## Goals - -- Explain what the project does today based on code, not assumptions. -- Give developers and AI agents a fast map of where to make changes. -- Capture system risks and incomplete areas so work is directed intentionally. - -## Document Index - -- `project-scope-analysis.md`: End-to-end feature and capability analysis. -- `architecture.md`: Runtime architecture, request flow, and service composition. -- `structure-and-boundaries.md`: Canonical placement rules for app vs framework code. -- `http-routes.md`: Route inventory grouped by access level and purpose. -- `data-model.md`: Ent entities and domain model coverage. -- `ai-agent-guide.md`: Practical guide for AI agents working in this repo. -- `known-gaps-and-risks.md`: Confirmed implementation gaps and technical risks. -- `development-workflows.md`: Day-to-day run/build/test/migration workflows. - -## Primary Source Files Used For This Analysis - -- `cmd/web/main.go` -- `cmd/worker/main.go` -- `cmd/seed/main.go` -- `pkg/services/container.go` -- `app/goship/web/routes/router.go` -- `app/goship/web/routes/*.go` -- `pkg/tasks/*.go` -- `pkg/repos/**/*.go` -- `ent/schema/*.go` -- `config/config.go` -- `config/config.yaml` -- `Makefile` -- `build.mjs` -- `package.json` -- `e2e_tests/tests/goship.spec.ts` diff --git a/docs/architecture.md b/docs/architecture/01-architecture.md similarity index 100% rename from docs/architecture.md rename to docs/architecture/01-architecture.md diff --git a/docs/structure-and-boundaries.md b/docs/architecture/02-structure-and-boundaries.md similarity index 100% rename from docs/structure-and-boundaries.md rename to docs/architecture/02-structure-and-boundaries.md diff --git a/docs/project-scope-analysis.md b/docs/architecture/03-project-scope-analysis.md similarity index 100% rename from docs/project-scope-analysis.md rename to docs/architecture/03-project-scope-analysis.md diff --git a/docs/http-routes.md b/docs/architecture/04-http-routes.md similarity index 100% rename from docs/http-routes.md rename to docs/architecture/04-http-routes.md diff --git a/docs/data-model.md b/docs/architecture/05-data-model.md similarity index 100% rename from docs/data-model.md rename to docs/architecture/05-data-model.md diff --git a/docs/known-gaps-and-risks.md b/docs/architecture/06-known-gaps-and-risks.md similarity index 100% rename from docs/known-gaps-and-risks.md rename to docs/architecture/06-known-gaps-and-risks.md diff --git a/docs/ai-agent-guide.md b/docs/guides/01-ai-agent-guide.md similarity index 86% rename from docs/ai-agent-guide.md rename to docs/guides/01-ai-agent-guide.md index 49e7f4a7..7ef3ff67 100644 --- a/docs/ai-agent-guide.md +++ b/docs/guides/01-ai-agent-guide.md @@ -4,7 +4,7 @@ This guide is for code agents making changes in this repository. ## Start Here -1. Read `docs/project-scope-analysis.md` and `docs/known-gaps-and-risks.md`. +1. Read `docs/architecture/03-project-scope-analysis.md` and `docs/architecture/06-known-gaps-and-risks.md`. 2. Inspect route wiring in `app/goship/web/routes/router.go` before editing handlers. 3. Inspect `pkg/services/container.go` before assuming a dependency is initialized. @@ -83,6 +83,6 @@ UI and rendering: When code behavior changes, update at least: -- `docs/project-scope-analysis.md` if capability changed -- `docs/http-routes.md` if route surface changed -- `docs/known-gaps-and-risks.md` if a risk was added/removed +- `docs/architecture/03-project-scope-analysis.md` if capability changed +- `docs/architecture/04-http-routes.md` if route surface changed +- `docs/architecture/06-known-gaps-and-risks.md` if a risk was added/removed diff --git a/docs/development-workflows.md b/docs/guides/02-development-workflows.md similarity index 100% rename from docs/development-workflows.md rename to docs/guides/02-development-workflows.md diff --git a/docs/policies/01-engineering-standards.md b/docs/policies/01-engineering-standards.md new file mode 100644 index 00000000..2a594d9f --- /dev/null +++ b/docs/policies/01-engineering-standards.md @@ -0,0 +1,89 @@ +# Repository Requirements (Engineering Standards) + +This file defines baseline requirements for any GoShip repository (framework, CLI, or app) to stay maintainable. + +## 1) Repository Layout + +1. Keep runtime/app code, framework code, and CLI code clearly separated. +2. Keep architecture and operational docs in `docs/`. +3. Keep one living plan document for roadmap/decisions. + +## 2) Local Developer Workflow + +Required commands must exist and work: + +1. `make dev` (web-only default) +2. `make test` (fast Docker-free unit set) +3. `make test-integration` (infra-backed integration set) +4. `make testall` (unit + integration) + +If CLI exists, equivalent commands must exist in CLI: + +1. `ship dev` +2. `ship test` +3. `ship test --integration` + +## 3) Pre-Commit Hooks (Required) + +Use `lefthook` with at least: + +1. unit test package set +2. formatting checks for touched languages +3. basic static checks (as adopted by repo stage) + +Rules: + +1. No bypass by default. +2. Hook runtime should stay fast (target under ~60s on normal changes). +3. Integration tests are not required in pre-commit. + +## 4) CI Requirements (Required) + +Every PR must run: + +1. lint/format checks +2. `make test` +3. selected integration tests (or full `make test-integration` where feasible) + +Main branch protection should require CI green before merge. + +## 5) Test Strategy + +1. Prefer table-driven unit tests. +2. Keep business logic testable without Docker where possible. +3. Use integration tests for external systems and process boundaries only. +4. Keep package-level coverage trending to 90%+ over time. + +## 6) Versioning and Tooling + +1. Pin project tools to declared versions (do not auto-latest on normal commands). +2. Provide a doctor/check command to detect version drift. +3. Use explicit upgrade workflows for intentional version bumps. + +## 7) Documentation Requirements + +For each behavior/architecture change: + +1. Update relevant `docs/*.md` files in the same change stream. +2. Keep CLI contract in `docs/reference/01-cli.md` current. +3. Keep framework plan (`docs/roadmap/01-framework-plan.md`) aligned with decisions. + +## 8) Commit and PR Standards + +1. Use conventional commit prefixes (`feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`). +2. Keep commits scoped and reviewable. +3. PR description must include: +: what changed +: why it changed +: test evidence +: docs updated + +## 9) Standalone Repo Readiness Checklist + +A module/repo is ready to stand alone when: + +1. it has its own `README` and usage commands +2. it has independent tests passing in CI +3. it has pinned toolchain policy documented +4. it has release/versioning policy documented +5. it can be developed with minimal implicit dependency on sibling repos diff --git a/docs/reference/01-cli.md b/docs/reference/01-cli.md new file mode 100644 index 00000000..bee69787 --- /dev/null +++ b/docs/reference/01-cli.md @@ -0,0 +1,81 @@ +# CLI Specification (Living) + +This file is the living CLI contract for developers and agents. + +Short command name: + +- `ship` + +Module location: + +- `cli/ship` (standalone Go module) +- binary entrypoint: `cli/ship/cmd/ship` + +Design constraints: + +1. Keep commands Rails-like and convention-first. +2. Keep v1 command set intentionally small. +3. Expand only after each command is stable and tested. + +## Minimal V1 Command Set + +Project lifecycle: + +- `ship new ` (planned) +- `ship doctor` (planned) + +Local runtime: + +- `ship dev` (web-only default) +- `ship dev --worker` +- `ship dev --all` + +Testing: + +- `ship test` (unit default) +- `ship test --integration` + +Database: + +- `ship db create` +- `ship db migrate` +- `ship db rollback` +- `ship db seed` + +Generation: + +- `ship generate ` (planned) +- `ship destroy ` (planned) + +## Versioning Rules + +1. CLI-managed tools (for example `templ`) must be pinned to project-declared versions. +2. `ship dev` and `ship test` must never auto-upgrade toolchain versions. +3. `ship doctor` reports version drift and prints explicit fix commands. +4. Only `ship upgrade` (future) may intentionally bump pinned versions. + +## Implementation Mapping (Current Repo) + +These commands are implemented as wrappers over existing workflows: + +- `ship dev` -> `make dev` +- `ship dev --worker` -> `make dev-worker` +- `ship dev --all` -> `make dev-full` +- `ship test` -> `make test` +- `ship test --integration` -> `make test-integration` +- `ship db create` -> `make up` +- `ship db migrate` -> `make migrate` +- `ship db rollback [amount]` -> `atlas migrate down ... [amount]` +- `ship db seed` -> `make seed` + +Local run examples from repository root: + +- `go run ./cli/ship/cmd/ship -- help` +- `go run ./cli/ship/cmd/ship -- dev` + +## Deferred (Not In V1) + +- `ship console` +- `ship routes` +- `ship upgrade` +- advanced generator variants diff --git a/README-framework-plan.md b/docs/roadmap/01-framework-plan.md similarity index 94% rename from README-framework-plan.md rename to docs/roadmap/01-framework-plan.md index 8ca5b51a..03c36209 100644 --- a/README-framework-plan.md +++ b/docs/roadmap/01-framework-plan.md @@ -26,9 +26,10 @@ Implementation and architecture documentation lives in `docs/`. Primary files for ongoing refactor work: -1. `docs/structure-and-boundaries.md` (canonical placement rules) -2. `docs/architecture.md` (runtime/system behavior) -3. `docs/ai-agent-guide.md` (agent execution conventions) +1. `docs/architecture/02-structure-and-boundaries.md` (canonical placement rules) +2. `docs/architecture/01-architecture.md` (runtime/system behavior) +3. `docs/guides/01-ai-agent-guide.md` (agent execution conventions) +4. `docs/reference/01-cli.md` (living `ship` CLI contract) Primary framing: @@ -96,6 +97,39 @@ CLI responsibilities: 4. `goship jobs backend set ` updates adapter config with capability checks. 5. `goship new` should auto-install templ tooling and keep it current in generated projects (including `go get -u github.com/a-h/templ` as part of bootstrap/update workflow). +### CLI Surface (Rails-Inspired) + +Primary command groups to match Rails ergonomics while staying Go-native: + +1. Project lifecycle: +- `goship new ` +- `goship upgrade` +- `goship doctor` +2. Runtime/developer workflow: +- `goship dev` (web-only default) +- `goship dev --worker` +- `goship dev --all` +- `goship test` (unit default) +- `goship test --integration` +3. Code generation: +- `goship generate ` +- `goship destroy ` +4. Modules/adapters: +- `goship module add ` +- `goship module remove ` +- `goship adapter set ` +5. Data/schema: +- `goship db:migrate` +- `goship db:rollback` +- `goship db:seed` + +Rules for versioned tooling in generated apps: + +1. CLI installs tool versions pinned to the project declaration. +2. CLI does not auto-upgrade tools to latest on dev/test commands. +3. `goship doctor` reports drift (e.g., templ CLI older/newer than project version) and provides fix commands. +4. `goship upgrade` is the only command that intentionally bumps pinned tool/module versions. + ## Core Product Goals 1. Rails-like productivity in Go. @@ -487,7 +521,7 @@ Proposed module boundaries: 4. `packages/notifications` 5. `packages/storage` 6. `packages/admin` -7. `cli/goship` +7. `cli/ship` ## Priority Roadmap diff --git a/go.work b/go.work new file mode 100644 index 00000000..be9a26b5 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.25.6 + +use ( + . + ./cli/ship +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..f0197557 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,314 @@ +cloud.google.com/go/accessapproval v1.7.7/go.mod h1:10ZDPYiTm8tgxuMPid8s2DL93BfCt6xBh/Vg0Xd8pU0= +cloud.google.com/go/accesscontextmanager v1.8.7/go.mod h1:jSvChL1NBQ+uLY9zUBdPy9VIlozPoHptdBnRYeWuQoM= +cloud.google.com/go/aiplatform v1.67.0/go.mod h1:s/sJ6btBEr6bKnrNWdK9ZgHCvwbZNdP90b3DDtxxw+Y= +cloud.google.com/go/analytics v0.23.2/go.mod h1:vtE3olAXZ6edJYk1UOndEs6EfaEc9T2B28Y4G5/a7Fo= +cloud.google.com/go/apigateway v1.6.7/go.mod h1:7wAMb/33Rzln+PrGK16GbGOfA1zAO5Pq6wp19jtIt7c= +cloud.google.com/go/apigeeconnect v1.6.7/go.mod h1:hZxCKvAvDdKX8+eT0g5eEAbRSS9Gkzi+MPWbgAMAy5U= +cloud.google.com/go/apigeeregistry v0.8.5/go.mod h1:ZMg60hq2K35tlqZ1VVywb9yjFzk9AJ7zqxrysOxLi3o= +cloud.google.com/go/appengine v1.8.7/go.mod h1:1Fwg2+QTgkmN6Y+ALGwV8INLbdkI7+vIvhcKPZCML0g= +cloud.google.com/go/area120 v0.8.7/go.mod h1:L/xTq4NLP9mmxiGdcsVz7y1JLc9DI8pfaXRXbnjkR6w= +cloud.google.com/go/artifactregistry v1.14.9/go.mod h1:n2OsUqbYoUI2KxpzQZumm6TtBgtRf++QulEohdnlsvI= +cloud.google.com/go/asset v1.19.1/go.mod h1:kGOS8DiCXv6wU/JWmHWCgaErtSZ6uN5noCy0YwVaGfs= +cloud.google.com/go/assuredworkloads v1.11.7/go.mod h1:CqXcRH9N0KCDtHhFisv7kk+cl//lyV+pYXGi1h8rCEU= +cloud.google.com/go/automl v1.13.7/go.mod h1:E+s0VOsYXUdXpq0y4gNZpi0A/s6y9+lAarmV5Eqlg40= +cloud.google.com/go/baremetalsolution v1.2.6/go.mod h1:KkS2BtYXC7YGbr42067nzFr+ABFMs6cxEcA1F+cedIw= +cloud.google.com/go/batch v1.8.6/go.mod h1:rQovrciYbtuY40Uprg/IWLlhmUR1GZYzX9xnymUdfBU= +cloud.google.com/go/beyondcorp v1.0.6/go.mod h1:wRkenqrVRtnGFfnyvIg0zBFUdN2jIfeojFF9JJDwVIA= +cloud.google.com/go/bigquery v1.61.0/go.mod h1:PjZUje0IocbuTOdq4DBOJLNYB0WF3pAKBHzAYyxCwFo= +cloud.google.com/go/billing v1.18.5/go.mod h1:lHw7fxS6p7hLWEPzdIolMtOd0ahLwlokW06BzbleKP8= +cloud.google.com/go/binaryauthorization v1.8.3/go.mod h1:Cul4SsGlbzEsWPOz2sH8m+g2Xergb6ikspUyQ7iOThE= +cloud.google.com/go/certificatemanager v1.8.1/go.mod h1:hDQzr50Vx2gDB+dOfmDSsQzJy/UPrYRdzBdJ5gAVFIc= +cloud.google.com/go/channel v1.17.7/go.mod h1:b+FkgBrhMKM3GOqKUvqHFY/vwgp+rwsAuaMd54wCdN4= +cloud.google.com/go/cloudbuild v1.16.1/go.mod h1:c2KUANTtCBD8AsRavpPout6Vx8W+fsn5zTsWxCpWgq4= +cloud.google.com/go/clouddms v1.7.6/go.mod h1:8HWZ2tznZ0mNAtTpfnRNT0QOThqn9MBUqTj0Lx8npIs= +cloud.google.com/go/cloudtasks v1.12.8/go.mod h1:aX8qWCtmVf4H4SDYUbeZth9C0n9dBj4dwiTYi4Or/P4= +cloud.google.com/go/compute v1.27.0 h1:EGawh2RUnfHT5g8f/FX3Ds6KZuIBC77hZoDrBvEZw94= +cloud.google.com/go/compute v1.27.0/go.mod h1:LG5HwRmWFKM2C5XxHRiNzkLLXW48WwvyVC0mfWsYPOM= +cloud.google.com/go/contactcenterinsights v1.13.2/go.mod h1:AfkSB8t7mt2sIY6WpfO61nD9J9fcidIchtxm9FqJVXk= +cloud.google.com/go/container v1.35.1/go.mod h1:udm8fgLm3TtpnjFN4QLLjZezAIIp/VnMo316yIRVRQU= +cloud.google.com/go/containeranalysis v0.11.6/go.mod h1:YRf7nxcTcN63/Kz9f86efzvrV33g/UV8JDdudRbYEUI= +cloud.google.com/go/datacatalog v1.20.1/go.mod h1:Jzc2CoHudhuZhpv78UBAjMEg3w7I9jHA11SbRshWUjk= +cloud.google.com/go/dataflow v0.9.7/go.mod h1:3BjkOxANrm1G3+/EBnEsTEEgJu1f79mFqoOOZfz3v+E= +cloud.google.com/go/dataform v0.9.4/go.mod h1:jjo4XY+56UrNE0wsEQsfAw4caUs4DLJVSyFBDelRDtQ= +cloud.google.com/go/datafusion v1.7.7/go.mod h1:qGTtQcUs8l51lFA9ywuxmZJhS4ozxsBSus6ItqCUWMU= +cloud.google.com/go/datalabeling v0.8.7/go.mod h1:/PPncW5gxrU15UzJEGQoOT3IobeudHGvoExrtZ8ZBwo= +cloud.google.com/go/dataplex v1.16.0/go.mod h1:OlBoytuQ56+7aUCC03D34CtoF/4TJ5SiIrLsBdDu87Q= +cloud.google.com/go/dataproc/v2 v2.4.2/go.mod h1:smGSj1LZP3wtnsM9eyRuDYftNAroAl6gvKp/Wk64XDE= +cloud.google.com/go/dataqna v0.8.7/go.mod h1:hvxGaSvINAVH5EJJsONIwT1y+B7OQogjHPjizOFoWOo= +cloud.google.com/go/datastore v1.17.0/go.mod h1:RiRZU0G6VVlIVlv1HRo3vSAPFHULV0ddBNsXO+Sony4= +cloud.google.com/go/datastream v1.10.6/go.mod h1:lPeXWNbQ1rfRPjBFBLUdi+5r7XrniabdIiEaCaAU55o= +cloud.google.com/go/deploy v1.19.0/go.mod h1:BW9vAujmxi4b/+S7ViEuYR65GiEsqL6Mhf5S/9TeDRU= +cloud.google.com/go/dialogflow v1.53.0/go.mod h1:LqAvxq7bXiiGC3/DWIz9XXCxth2z2qpSnBAAmlNOj6U= +cloud.google.com/go/dlp v1.14.0/go.mod h1:4fvEu3EbLsHrgH3QFdFlTNIiCP5mHwdYhS/8KChDIC4= +cloud.google.com/go/documentai v1.29.0/go.mod h1:3Qt8PMt3S8W6w3VeoYFraaMS2GJRrXFnvkyn+GpB1n0= +cloud.google.com/go/domains v0.9.7/go.mod h1:u/yVf3BgfPJW3QDZl51qTJcDXo9PLqnEIxfGmGgbHEc= +cloud.google.com/go/edgecontainer v1.2.1/go.mod h1:OE2D0lbkmGDVYLCvpj8Y0M4a4K076QB7E2JupqOR/qU= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.6.8/go.mod h1:EHONVDSum2xxG2p+myyVda/FwwvGbY58ZYC4XqI/lDQ= +cloud.google.com/go/eventarc v1.13.6/go.mod h1:QReOaYnDNdjwAQQWNC7nfr63WnaKFUw7MSdQ9PXJYj0= +cloud.google.com/go/filestore v1.8.3/go.mod h1:QTpkYpKBF6jlPRmJwhLqXfJQjVrQisplyb4e2CwfJWc= +cloud.google.com/go/functions v1.16.2/go.mod h1:+gMvV5E3nMb9EPqX6XwRb646jTyVz8q4yk3DD6xxHpg= +cloud.google.com/go/gkebackup v1.5.0/go.mod h1:eLaf/+n8jEmIvOvDriGjo99SN7wRvVadoqzbZu0WzEw= +cloud.google.com/go/gkeconnect v0.8.7/go.mod h1:iUH1jgQpTyNFMK5LgXEq2o0beIJ2p7KKUUFerkf/eGc= +cloud.google.com/go/gkehub v0.14.7/go.mod h1:NLORJVTQeCdxyAjDgUwUp0A6BLEaNLq84mCiulsM4OE= +cloud.google.com/go/gkemulticloud v1.2.0/go.mod h1:iN5wBxTLPR6VTBWpkUsOP2zuPOLqZ/KbgG1bZir1Cng= +cloud.google.com/go/gsuiteaddons v1.6.7/go.mod h1:u+sGBvr07OKNnOnQiB/Co1q4U2cjo50ERQwvnlcpNis= +cloud.google.com/go/iap v1.9.6/go.mod h1:YiK+tbhDszhaVifvzt2zTEF2ch9duHtp6xzxj9a0sQk= +cloud.google.com/go/ids v1.4.7/go.mod h1:yUkDC71u73lJoTaoONy0dsA0T7foekvg6ZRg9IJL0AA= +cloud.google.com/go/iot v1.7.7/go.mod h1:tr0bCOSPXtsg64TwwZ/1x+ReTWKlQRVXbM+DnrE54yM= +cloud.google.com/go/kms v1.17.1/go.mod h1:DCMnCF/apA6fZk5Cj4XsD979OyHAqFasPuA5Sd0kGlQ= +cloud.google.com/go/language v1.12.5/go.mod h1:w/6a7+Rhg6Bc2Uzw6thRdKKNjnOzfKTJuxzD0JZZ0nM= +cloud.google.com/go/lifesciences v0.9.7/go.mod h1:FQ713PhjAOHqUVnuwsCe1KPi9oAdaTfh58h1xPiW13g= +cloud.google.com/go/logging v1.10.0/go.mod h1:EHOwcxlltJrYGqMGfghSet736KR3hX1MAj614mrMk9I= +cloud.google.com/go/managedidentities v1.6.7/go.mod h1:UzslJgHnc6luoyx2JV19cTCi2Fni/7UtlcLeSYRzTV8= +cloud.google.com/go/maps v1.11.0/go.mod h1:XcSsd8lg4ZhLPCtJ2YHcu/xLVePBzZOlI7GmR2cRCws= +cloud.google.com/go/mediatranslation v0.8.7/go.mod h1:6eJbPj1QJwiCP8R4K413qMx6ZHZJUi9QFpApqY88xWU= +cloud.google.com/go/memcache v1.10.7/go.mod h1:SrU6+QBhvXJV0TA59+B3oCHtLkPx37eqdKmRUlmSE1k= +cloud.google.com/go/metastore v1.13.6/go.mod h1:OBCVMCP7X9vA4KKD+5J4Q3d+tiyKxalQZnksQMq5MKY= +cloud.google.com/go/monitoring v1.19.0/go.mod h1:25IeMR5cQ5BoZ8j1eogHE5VPJLlReQ7zFp5OiLgiGZw= +cloud.google.com/go/networkconnectivity v1.14.6/go.mod h1:/azB7+oCSmyBs74Z26EogZ2N3UcXxdCHkCPcz8G32bU= +cloud.google.com/go/networkmanagement v1.13.2/go.mod h1:24VrV/5HFIOXMEtVQEUoB4m/w8UWvUPAYjfnYZcBc4c= +cloud.google.com/go/networksecurity v0.9.7/go.mod h1:aB6UiPnh/l32+TRvgTeOxVRVAHAFFqvK+ll3idU5BoY= +cloud.google.com/go/notebooks v1.11.5/go.mod h1:pz6P8l2TvhWqAW3sysIsS0g2IUJKOzEklsjWJfi8sd4= +cloud.google.com/go/optimization v1.6.5/go.mod h1:eiJjNge1NqqLYyY75AtIGeQWKO0cvzD1ct/moCFaP2Q= +cloud.google.com/go/orchestration v1.9.2/go.mod h1:8bGNigqCQb/O1kK7PeStSNlyi58rQvZqDiuXT9KAcbg= +cloud.google.com/go/orgpolicy v1.12.3/go.mod h1:6BOgIgFjWfJzTsVcib/4QNHOAeOjCdaBj69aJVs//MA= +cloud.google.com/go/osconfig v1.12.7/go.mod h1:ID7Lbqr0fiihKMwAOoPomWRqsZYKWxfiuafNZ9j1Y1M= +cloud.google.com/go/oslogin v1.13.3/go.mod h1:WW7Rs1OJQ1iSUckZDilvNBSNPE8on740zF+4ZDR4o8U= +cloud.google.com/go/phishingprotection v0.8.7/go.mod h1:FtYaOyGc/HQQU7wY4sfwYZBFDKAL+YtVBjUj8E3A3/I= +cloud.google.com/go/policytroubleshooter v1.10.5/go.mod h1:bpOf94YxjWUqsVKokzPBibMSAx937Jp2UNGVoMAtGYI= +cloud.google.com/go/privatecatalog v0.9.7/go.mod h1:NWLa8MCL6NkRSt8jhL8Goy2A/oHkvkeAxiA0gv0rIXI= +cloud.google.com/go/pubsub v1.38.0/go.mod h1:IPMJSWSus/cu57UyR01Jqa/bNOQA+XnPF6Z4dKW4fAA= +cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= +cloud.google.com/go/recaptchaenterprise/v2 v2.13.0/go.mod h1:jNYyn2ScR4DTg+VNhjhv/vJQdaU8qz+NpmpIzEE7HFQ= +cloud.google.com/go/recommendationengine v0.8.7/go.mod h1:YsUIbweUcpm46OzpVEsV5/z+kjuV6GzMxl7OAKIGgKE= +cloud.google.com/go/recommender v1.12.3/go.mod h1:OgN0MjV7/6FZUUPgF2QPQtYErtZdZc4u+5onvurcGEI= +cloud.google.com/go/redis v1.15.0/go.mod h1:X9Fp3vG5kqr5ho+5YM6AgJxypn+I9Ea5ANCuFKXLdX0= +cloud.google.com/go/resourcemanager v1.9.7/go.mod h1:cQH6lJwESufxEu6KepsoNAsjrUtYYNXRwxm4QFE5g8A= +cloud.google.com/go/resourcesettings v1.6.7/go.mod h1:zwRL5ZoNszs1W6+eJYMk6ILzgfnTj13qfU4Wvfupuqk= +cloud.google.com/go/retail v1.16.2/go.mod h1:T7UcBh4/eoxRBpP3vwZCoa+PYA9/qWRTmOCsV8DRdZ0= +cloud.google.com/go/run v1.3.7/go.mod h1:iEUflDx4Js+wK0NzF5o7hE9Dj7QqJKnRj0/b6rhVq20= +cloud.google.com/go/scheduler v1.10.8/go.mod h1:0YXHjROF1f5qTMvGTm4o7GH1PGAcmu/H/7J7cHOiHl0= +cloud.google.com/go/secretmanager v1.13.1/go.mod h1:y9Ioh7EHp1aqEKGYXk3BOC+vkhlHm9ujL7bURT4oI/4= +cloud.google.com/go/security v1.17.0/go.mod h1:eSuFs0SlBv1gWg7gHIoF0hYOvcSwJCek/GFXtgO6aA0= +cloud.google.com/go/securitycenter v1.30.0/go.mod h1:/tmosjS/dfTnzJxOzZhTXdX3MXWsCmPWfcYOgkJmaJk= +cloud.google.com/go/servicedirectory v1.11.7/go.mod h1:fiO/tM0jBpVhpCAe7Yp5HmEsmxSUcOoc4vPrO02v68I= +cloud.google.com/go/shell v1.7.7/go.mod h1:7OYaMm3TFMSZBh8+QYw6Qef+fdklp7CjjpxYAoJpZbQ= +cloud.google.com/go/spanner v1.63.0/go.mod h1:iqDx7urZpgD7RekZ+CFvBRH6kVTW1ZSEb2HMDKOp5Cc= +cloud.google.com/go/speech v1.23.1/go.mod h1:UNgzNxhNBuo/OxpF1rMhA/U2rdai7ILL6PBXFs70wq0= +cloud.google.com/go/storagetransfer v1.10.6/go.mod h1:3sAgY1bx1TpIzfSzdvNGHrGYldeCTyGI/Rzk6Lc6A7w= +cloud.google.com/go/talent v1.6.8/go.mod h1:kqPAJvhxmhoUTuqxjjk2KqA8zUEeTDmH+qKztVubGlQ= +cloud.google.com/go/texttospeech v1.7.7/go.mod h1:XO4Wr2VzWHjzQpMe3gS58Oj68nmtXMyuuH+4t0wy9eA= +cloud.google.com/go/tpu v1.6.7/go.mod h1:o8qxg7/Jgt7TCgZc3jNkd4kTsDwuYD3c4JTMqXZ36hU= +cloud.google.com/go/trace v1.10.7/go.mod h1:qk3eiKmZX0ar2dzIJN/3QhY2PIFh1eqcIdaN5uEjQPM= +cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= +cloud.google.com/go/video v1.21.0/go.mod h1:Kqh97xHXZ/bIClgDHf5zkKvU3cvYnLyRefmC8yCBqKI= +cloud.google.com/go/videointelligence v1.11.7/go.mod h1:iMCXbfjurmBVgKuyLedTzv90kcnppOJ6ttb0+rLDID0= +cloud.google.com/go/vision/v2 v2.8.2/go.mod h1:BHZA1LC7dcHjSr9U9OVhxMtLKd5l2jKPzLRALEJvuaw= +cloud.google.com/go/vmmigration v1.7.7/go.mod h1:qYIK5caZY3IDMXQK+A09dy81QU8qBW0/JDTc39OaKRw= +cloud.google.com/go/vmwareengine v1.1.3/go.mod h1:UoyF6LTdrIJRvDN8uUB8d0yimP5A5Ehkr1SRzL1APZw= +cloud.google.com/go/vpcaccess v1.7.7/go.mod h1:EzfSlgkoAnFWEMznZW0dVNvdjFjEW97vFlKk4VNBhwY= +cloud.google.com/go/webrisk v1.9.7/go.mod h1:7FkQtqcKLeNwXCdhthdXHIQNcFWPF/OubrlyRcLHNuQ= +cloud.google.com/go/websecurityscanner v1.6.7/go.mod h1:EpiW84G5KXxsjtFKK7fSMQNt8JcuLA8tQp7j0cyV458= +cloud.google.com/go/workflows v1.12.6/go.mod h1:oDbEHKa4otYg4abwdw2Z094jB0TLLiFGAPA78EDAKag= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/casbin/casbin/v2 v2.64.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/container-orchestrated-devices/container-device-interface v0.6.1/go.mod h1:40T6oW59rFrL/ksiSs7q45GzjGlbvxnA4xaK6cyq+kA= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs/v2 v2.0.0/go.mod h1:swkD/7j9HApWpzl8OHfrHNxppPd9l44DFZdF94BUj9k= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/go-cni v1.1.9/go.mod h1:XYrZJ1d5W6E2VOvjffL3IZq0Dz6bsVlERHbekNK90PM= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.1.7/go.mod h1:FD8gqIcX5aTotCtOmjeCsi3A1dHmTZpnMISGKSczt4k= +github.com/containerd/nri v0.4.0/go.mod h1:Zw9q2lP16sdg0zYybemZ9yTDy8g7fPCIB3KXOGlggXI= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/zfs v1.1.0/go.mod h1:oZF9wBnrnQjpWLaPKEinrx3TQ9a+W/RJO7Zb41d8YLE= +github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4= +github.com/containers/ocicrypt v1.1.6/go.mod h1:WgjxPWdTJMqYMjf3M6cuIFFA1/MpyyhIM99YInA+Rvc= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/docker/cli v23.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= +github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/intel/goresctrl v0.3.0/go.mod h1:fdz3mD85cmP9sHD8JUlrNWAxvwM86CrbmVXltEKd7zk= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= +github.com/kataras/golog v0.1.8/go.mod h1:rGPAin4hYROfk1qT9wZP6VY2rsb4zzc37QpdPjdkqVw= +github.com/kataras/iris/v12 v12.2.0/go.mod h1:BLzBpEunc41GbE68OUaQlqX4jzi791mx5HU04uPb90Y= +github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/open-policy-agent/opa v0.42.2/go.mod h1:MrmoTi/BsKWT58kXlVayBb+rYVeaMwuBm3nYAN3923s= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= +github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/crypt v0.15.0/go.mod h1:5rwNNax6Mlk9sZ40AcyVtiEw24Z4J04cfSioF2COKmc= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= +github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/vektah/gqlparser/v2 v2.4.5/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/veraison/go-cose v1.0.0-rc.1/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.9/go.mod h1:0NBdNx9wbxtEQLwAQtrDHwx58m02vXpDcgSYI2seohQ= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240528184218-531527333157/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/cri-api v0.27.1/go.mod h1:+Ts/AVYbIo04S86XbTD73UPp/DkTiYxtsFeOFEu32L0= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 92fb00fddf57fbfdcf5830216f9bb5269e3114bc Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 17:20:11 -0800 Subject: [PATCH 011/291] docs: clarify ship CLI monorepo placement and ownership --- .../02-structure-and-boundaries.md | 12 +++++-- docs/reference/01-cli.md | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/docs/architecture/02-structure-and-boundaries.md b/docs/architecture/02-structure-and-boundaries.md index ecc9d8a0..7e26d0bb 100644 --- a/docs/architecture/02-structure-and-boundaries.md +++ b/docs/architecture/02-structure-and-boundaries.md @@ -2,15 +2,22 @@ This document defines where code belongs as GoShip evolves into a Rails-like framework plus example app. -## Current Top-Level Shape +## Current Top-Level Shape (Single Repository) - `app/goship/`: app-specific code for the first-party GoShip app -- `cmd/`: runnable entrypoints (`web`, `worker`, `seed`, CLI) +- `cmd/`: runnable entrypoints for the app module (`web`, `worker`, `seed`) +- `cli/ship/`: standalone CLI module (`ship`) that lives in this same repository - `pkg/`: reusable framework-level libraries and adapters - `config/`: runtime configuration - `ent/`: schema and generated ORM - `docs/`: internal design and implementation documentation +Monorepo note: + +- GoShip currently uses one repository with multiple Go modules: +- root app/framework module + `cli/ship` module +- `go.work` ties local development across modules together for maintainers. + ## App vs Framework Rules Use this placement rule for every new file: @@ -46,4 +53,3 @@ These remain framework-level until each package is classified as either: - app-specific (move under `app/goship/...`), or - reusable framework module (stay in `pkg/...` or move to future dedicated framework modules). - diff --git a/docs/reference/01-cli.md b/docs/reference/01-cli.md index bee69787..6689f23a 100644 --- a/docs/reference/01-cli.md +++ b/docs/reference/01-cli.md @@ -11,6 +11,21 @@ Module location: - `cli/ship` (standalone Go module) - binary entrypoint: `cli/ship/cmd/ship` +## Repository Placement + +The CLI is in the same repository as the framework and example app. + +- Repo model: monorepo with multiple Go modules. +- App/framework module: repository root. +- CLI module: `cli/ship`. +- Workspace: `go.work` includes both modules for local development. + +Why this shape: + +1. Single repo keeps framework, app example, and CLI evolution in sync. +2. Separate CLI module keeps dependency graph and release surface clean. +3. Developers can iterate across modules locally without publishing interim versions. + Design constraints: 1. Keep commands Rails-like and convention-first. @@ -73,6 +88,22 @@ Local run examples from repository root: - `go run ./cli/ship/cmd/ship -- help` - `go run ./cli/ship/cmd/ship -- dev` +## Ownership Boundaries + +CLI owns: + +- developer command interface (`ship ...`); +- orchestration of dev/test/db workflows; +- version/tooling checks and future generators. + +App/framework owns: + +- actual runtime behavior in `cmd/*`, `app/goship/*`, `pkg/*`, and `config/*`. + +Rule: + +- keep business/runtime logic out of CLI package; CLI should call stable commands/APIs. + ## Deferred (Not In V1) - `ship console` From 50b181bd5699a7cc406efd217646cee8a04633bc Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 17:40:21 -0800 Subject: [PATCH 012/291] feat(mcp): add ship MCP module and release workflows --- .github/workflows/release-ship-mcp.yml | 34 ++ .github/workflows/release-ship.yml | 34 ++ .goreleaser.ship-mcp.yml | 28 ++ .goreleaser.ship.yml | 28 ++ docs/00-index.md | 1 + .../02-structure-and-boundaries.md | 3 +- docs/reference/01-cli.md | 1 + docs/reference/02-mcp.md | 128 ++++++++ go.work | 1 + mcp/ship/README.md | 31 ++ mcp/ship/cmd/ship-mcp/main.go | 20 ++ mcp/ship/go.mod | 3 + mcp/ship/internal/server/server.go | 178 ++++++++++ mcp/ship/internal/server/server_test.go | 32 ++ mcp/ship/internal/server/tools.go | 310 ++++++++++++++++++ mcp/ship/internal/server/tools_test.go | 130 ++++++++ 16 files changed, 961 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-ship-mcp.yml create mode 100644 .github/workflows/release-ship.yml create mode 100644 .goreleaser.ship-mcp.yml create mode 100644 .goreleaser.ship.yml create mode 100644 docs/reference/02-mcp.md create mode 100644 mcp/ship/README.md create mode 100644 mcp/ship/cmd/ship-mcp/main.go create mode 100644 mcp/ship/go.mod create mode 100644 mcp/ship/internal/server/server.go create mode 100644 mcp/ship/internal/server/server_test.go create mode 100644 mcp/ship/internal/server/tools.go create mode 100644 mcp/ship/internal/server/tools_test.go diff --git a/.github/workflows/release-ship-mcp.yml b/.github/workflows/release-ship-mcp.yml new file mode 100644 index 00000000..a52f53ec --- /dev/null +++ b/.github/workflows/release-ship-mcp.yml @@ -0,0 +1,34 @@ +name: release-ship-mcp + +on: + push: + tags: + - "ship-mcp/v*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25.6" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean --config .goreleaser.ship-mcp.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/release-ship.yml b/.github/workflows/release-ship.yml new file mode 100644 index 00000000..212c28de --- /dev/null +++ b/.github/workflows/release-ship.yml @@ -0,0 +1,34 @@ +name: release-ship + +on: + push: + tags: + - "ship/v*" + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25.6" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean --config .goreleaser.ship.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.goreleaser.ship-mcp.yml b/.goreleaser.ship-mcp.yml new file mode 100644 index 00000000..ebde3d7f --- /dev/null +++ b/.goreleaser.ship-mcp.yml @@ -0,0 +1,28 @@ +project_name: ship-mcp + +builds: + - id: ship-mcp + main: ./mcp/ship/cmd/ship-mcp + binary: ship-mcp + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + +archives: + - id: ship-mcp-archives + builds: + - ship-mcp + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: "ship-mcp_checksums.txt" + diff --git a/.goreleaser.ship.yml b/.goreleaser.ship.yml new file mode 100644 index 00000000..e7eb85fe --- /dev/null +++ b/.goreleaser.ship.yml @@ -0,0 +1,28 @@ +project_name: ship + +builds: + - id: ship + main: ./cli/ship/cmd/ship + binary: ship + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + +archives: + - id: ship-archives + builds: + - ship + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: "ship_checksums.txt" + diff --git a/docs/00-index.md b/docs/00-index.md index 583c058b..e8264886 100644 --- a/docs/00-index.md +++ b/docs/00-index.md @@ -33,6 +33,7 @@ It is not intended as user-facing product documentation. ### Reference 1. `R01` - `reference/01-cli.md`: living CLI specification (`ship`) for developers and agents. +2. `R02` - `reference/02-mcp.md`: living MCP spec (`ship-mcp`) for LLM-facing docs and CLI support. ### Policies diff --git a/docs/architecture/02-structure-and-boundaries.md b/docs/architecture/02-structure-and-boundaries.md index 7e26d0bb..4f050c0c 100644 --- a/docs/architecture/02-structure-and-boundaries.md +++ b/docs/architecture/02-structure-and-boundaries.md @@ -7,6 +7,7 @@ This document defines where code belongs as GoShip evolves into a Rails-like fra - `app/goship/`: app-specific code for the first-party GoShip app - `cmd/`: runnable entrypoints for the app module (`web`, `worker`, `seed`) - `cli/ship/`: standalone CLI module (`ship`) that lives in this same repository +- `mcp/ship/`: standalone MCP module (`ship-mcp`) for LLM-facing docs/CLI tooling - `pkg/`: reusable framework-level libraries and adapters - `config/`: runtime configuration - `ent/`: schema and generated ORM @@ -15,7 +16,7 @@ This document defines where code belongs as GoShip evolves into a Rails-like fra Monorepo note: - GoShip currently uses one repository with multiple Go modules: -- root app/framework module + `cli/ship` module +- root app/framework module + `cli/ship` module + `mcp/ship` module - `go.work` ties local development across modules together for maintainers. ## App vs Framework Rules diff --git a/docs/reference/01-cli.md b/docs/reference/01-cli.md index 6689f23a..458a5db1 100644 --- a/docs/reference/01-cli.md +++ b/docs/reference/01-cli.md @@ -10,6 +10,7 @@ Module location: - `cli/ship` (standalone Go module) - binary entrypoint: `cli/ship/cmd/ship` +- companion MCP module: `mcp/ship` (for LLM-facing tool access) ## Repository Placement diff --git a/docs/reference/02-mcp.md b/docs/reference/02-mcp.md new file mode 100644 index 00000000..80fced75 --- /dev/null +++ b/docs/reference/02-mcp.md @@ -0,0 +1,128 @@ +# MCP Server (ship-mcp) + +This document defines the minimal MCP server currently shipped in this repository. + +## Location + +- Module: `mcp/ship` +- Entrypoint: `mcp/ship/cmd/ship-mcp/main.go` +- Workspace wiring: `go.work` + +This is a standalone Go module in the same monorepo as: + +- app/framework module at repo root +- CLI module at `cli/ship` + +## Purpose + +Provide an LLM-facing interface for: + +1. `ship` command usage guidance. +2. focused access to docs under `docs/`. + +## Current Tool Set (V1) + +1. `ship_help` +- Returns usage/help text for core `ship` commands. +- Input: optional `topic` (`general`, `dev`, `test`, `db`). + +2. `docs_search` +- Searches markdown files under `docs/`. +- Input: `query` (required), `limit` (optional, default 20, max 50). + +3. `docs_get` +- Returns a single markdown document from `docs/`. +- Input: `path` (required), relative to `docs/`. + +## Runtime Notes + +- Transport: stdio with MCP JSON-RPC framing (`Content-Length` headers). +- Default docs root: `docs`. +- Override docs root with `SHIP_MCP_DOCS_ROOT`. + +Run from repo root: + +```bash +go run ./mcp/ship/cmd/ship-mcp +``` + +## Install In MCP Clients + +Build a reusable local binary first: + +```bash +go build -o ~/.local/bin/ship-mcp ./mcp/ship/cmd/ship-mcp +``` + +Use an absolute docs path: + +```bash +export SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs +``` + +Register in Codex: + +```bash +codex mcp add ship --env SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs -- ~/.local/bin/ship-mcp +codex mcp list +``` + +Register in Claude Code: + +```bash +claude mcp add --scope user -e SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs ship -- ~/.local/bin/ship-mcp +claude mcp list +``` + +Register in Gemini CLI: + +```bash +gemini mcp add --scope user -e SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs ship ~/.local/bin/ship-mcp +gemini mcp list +``` + +Notes: + +1. `--scope user` makes this MCP server available globally; use project/local scope for repo-only registration. +2. Restart your CLI session after adding a new MCP server. +3. If `~/.local/bin` is not on `PATH`, keep using the absolute binary path. + +## Release Binaries (MCP Internals) + +`ship-mcp` prebuilt binaries are published from this repository using GoReleaser and GitHub Actions. + +Internal release files: + +- `.goreleaser.ship-mcp.yml` +- `.github/workflows/release-ship-mcp.yml` + +Workflow trigger policy: + +1. Tag push only: `ship-mcp/v*` +2. Manual trigger allowed: `workflow_dispatch` +3. No release on regular branch pushes + +Tag example: + +```bash +git tag ship-mcp/v0.1.0 +git push origin ship-mcp/v0.1.0 +``` + +What gets published: + +1. `ship-mcp` binaries for `linux`, `darwin`, `windows` +2. Architectures: `amd64`, `arm64` +3. Checksums file: `ship-mcp_checksums.txt` + +## Safety Rules + +1. `docs_get` only reads inside `docs/` (path traversal blocked). +2. large doc payloads are truncated. +3. unknown methods/tools return structured JSON-RPC errors. + +## Near-Term Extensions + +1. Add a `ship_run` tool with allowlisted commands. +2. Add doc IDs that map to numbered filenames for stable retrieval. +3. Add cross-doc link graph output for faster agent navigation. diff --git a/go.work b/go.work index be9a26b5..210e54b9 100644 --- a/go.work +++ b/go.work @@ -3,4 +3,5 @@ go 1.25.6 use ( . ./cli/ship + ./mcp/ship ) diff --git a/mcp/ship/README.md b/mcp/ship/README.md new file mode 100644 index 00000000..ad956ce5 --- /dev/null +++ b/mcp/ship/README.md @@ -0,0 +1,31 @@ +# ship-mcp + +Minimal MCP server for GoShip docs and CLI guidance. + +## Location + +This MCP server lives in the same repository as the app/framework and CLI: + +- app/framework module: repo root +- CLI module: `cli/ship` +- MCP module: `mcp/ship` + +## Run + +From repository root: + +```bash +go run ./mcp/ship/cmd/ship-mcp +``` + +Optional docs root override: + +```bash +SHIP_MCP_DOCS_ROOT=docs go run ./mcp/ship/cmd/ship-mcp +``` + +## Tools + +- `ship_help`: return usage text for `ship` commands. +- `docs_search`: search markdown docs under `docs/`. +- `docs_get`: fetch one markdown doc by path. diff --git a/mcp/ship/cmd/ship-mcp/main.go b/mcp/ship/cmd/ship-mcp/main.go new file mode 100644 index 00000000..9e9be698 --- /dev/null +++ b/mcp/ship/cmd/ship-mcp/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/mikestefanello/pagoda/mcp/ship/internal/server" +) + +func main() { + docsRoot := os.Getenv("SHIP_MCP_DOCS_ROOT") + if docsRoot == "" { + docsRoot = "docs" + } + + if err := server.Run(context.Background(), os.Stdin, os.Stdout, os.Stderr, docsRoot); err != nil { + log.Fatal(err) + } +} diff --git a/mcp/ship/go.mod b/mcp/ship/go.mod new file mode 100644 index 00000000..33d73777 --- /dev/null +++ b/mcp/ship/go.mod @@ -0,0 +1,3 @@ +module github.com/mikestefanello/pagoda/mcp/ship + +go 1.25.6 diff --git a/mcp/ship/internal/server/server.go b/mcp/ship/internal/server/server.go new file mode 100644 index 00000000..6caf6658 --- /dev/null +++ b/mcp/ship/internal/server/server.go @@ -0,0 +1,178 @@ +package server + +import ( + "bufio" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +const protocolVersion = "2024-11-05" + +// Run serves a minimal MCP server over stdio. +func Run(ctx context.Context, in io.Reader, out io.Writer, log io.Writer, docsRoot string) error { + s := &mcpServer{docsRoot: docsRoot, out: out, log: log} + reader := bufio.NewReader(in) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + body, err := readMessage(reader) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + + var req rpcRequest + if err := json.Unmarshal(body, &req); err != nil { + _ = s.writeError(nil, -32700, "parse error") + continue + } + + if len(req.ID) == 0 { + // Notification: accept and ignore unknown notifications. + continue + } + + if err := s.handleRequest(req); err != nil { + return err + } + } +} + +type mcpServer struct { + docsRoot string + out io.Writer + log io.Writer +} + +type rpcRequest struct { + JSONRPC string `json:"jsonrpc"` + ID json.RawMessage `json:"id,omitempty"` + Method string `json:"method"` + Params json.RawMessage `json:"params,omitempty"` +} + +type rpcResponse struct { + JSONRPC string `json:"jsonrpc"` + ID json.RawMessage `json:"id,omitempty"` + Result any `json:"result,omitempty"` + Error *rpcErrorObject `json:"error,omitempty"` +} + +type rpcErrorObject struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (s *mcpServer) handleRequest(req rpcRequest) error { + switch req.Method { + case "initialize": + result := map[string]any{ + "protocolVersion": protocolVersion, + "capabilities": map[string]any{ + "tools": map[string]any{}, + }, + "serverInfo": map[string]any{ + "name": "ship-mcp", + "version": "0.1.0", + }, + } + return s.writeResult(req.ID, result) + case "ping": + return s.writeResult(req.ID, map[string]any{}) + case "tools/list": + return s.writeResult(req.ID, map[string]any{"tools": toolDefinitions()}) + case "tools/call": + result, err := s.handleToolsCall(req.Params) + if err != nil { + return s.writeError(req.ID, -32602, err.Error()) + } + return s.writeResult(req.ID, result) + case "shutdown": + return s.writeResult(req.ID, map[string]any{}) + default: + return s.writeError(req.ID, -32601, "method not found") + } +} + +func (s *mcpServer) writeResult(id json.RawMessage, result any) error { + return s.writeResponse(rpcResponse{ + JSONRPC: "2.0", + ID: id, + Result: result, + }) +} + +func (s *mcpServer) writeError(id json.RawMessage, code int, message string) error { + return s.writeResponse(rpcResponse{ + JSONRPC: "2.0", + ID: id, + Error: &rpcErrorObject{ + Code: code, + Message: message, + }, + }) +} + +func (s *mcpServer) writeResponse(resp rpcResponse) error { + data, err := json.Marshal(resp) + if err != nil { + return err + } + + header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(data)) + if _, err := io.WriteString(s.out, header); err != nil { + return err + } + _, err = s.out.Write(data) + return err +} + +func readMessage(r *bufio.Reader) ([]byte, error) { + contentLength := -1 + for { + line, err := r.ReadString('\n') + if err != nil { + return nil, err + } + line = strings.TrimRight(line, "\r\n") + if line == "" { + break + } + + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + name := strings.TrimSpace(strings.ToLower(parts[0])) + value := strings.TrimSpace(parts[1]) + if name == "content-length" { + n, err := strconv.Atoi(value) + if err != nil { + return nil, fmt.Errorf("invalid content-length: %w", err) + } + contentLength = n + } + } + + if contentLength < 0 { + return nil, errors.New("missing content-length header") + } + + body := make([]byte, contentLength) + if _, err := io.ReadFull(r, body); err != nil { + return nil, err + } + return body, nil +} diff --git a/mcp/ship/internal/server/server_test.go b/mcp/ship/internal/server/server_test.go new file mode 100644 index 00000000..88fd963b --- /dev/null +++ b/mcp/ship/internal/server/server_test.go @@ -0,0 +1,32 @@ +package server + +import ( + "bufio" + "bytes" + "fmt" + "testing" +) + +func TestReadMessage(t *testing.T) { + t.Parallel() + + body := []byte(`{"jsonrpc":"2.0","id":1,"method":"ping"}`) + data := []byte(fmt.Sprintf("Content-Length: %d\r\n\r\n%s", len(body), body)) + + got, err := readMessage(bufio.NewReader(bytes.NewReader(data))) + if err != nil { + t.Fatalf("readMessage error: %v", err) + } + if string(got) != string(body) { + t.Fatalf("got %q want %q", string(got), string(body)) + } +} + +func TestReadMessageMissingLength(t *testing.T) { + t.Parallel() + + _, err := readMessage(bufio.NewReader(bytes.NewReader([]byte("Header: x\r\n\r\n{}")))) + if err == nil { + t.Fatal("expected error") + } +} diff --git a/mcp/ship/internal/server/tools.go b/mcp/ship/internal/server/tools.go new file mode 100644 index 00000000..be3991a1 --- /dev/null +++ b/mcp/ship/internal/server/tools.go @@ -0,0 +1,310 @@ +package server + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" +) + +type toolCallParams struct { + Name string `json:"name"` + Arguments json.RawMessage `json:"arguments,omitempty"` +} + +type toolContent struct { + Type string `json:"type"` + Text string `json:"text"` +} + +type toolCallResult struct { + Content []toolContent `json:"content"` + IsError bool `json:"isError,omitempty"` +} + +func toolDefinitions() []map[string]any { + return []map[string]any{ + { + "name": "ship_help", + "description": "Get usage/help text for ship CLI commands.", + "inputSchema": map[string]any{ + "type": "object", + "properties": map[string]any{ + "topic": map[string]any{ + "type": "string", + "enum": []string{"general", "dev", "test", "db"}, + "description": "Optional help topic.", + }, + }, + }, + }, + { + "name": "docs_search", + "description": "Search markdown docs under docs/ and return matching lines.", + "inputSchema": map[string]any{ + "type": "object", + "properties": map[string]any{ + "query": map[string]any{ + "type": "string", + "description": "Case-insensitive text to find.", + }, + "limit": map[string]any{ + "type": "integer", + "description": "Maximum number of matches (default 20, max 50).", + }, + }, + "required": []string{"query"}, + }, + }, + { + "name": "docs_get", + "description": "Read one markdown doc by relative path under docs/.", + "inputSchema": map[string]any{ + "type": "object", + "properties": map[string]any{ + "path": map[string]any{ + "type": "string", + "description": "Path under docs/, for example architecture/01-architecture.md.", + }, + }, + "required": []string{"path"}, + }, + }, + } +} + +func (s *mcpServer) handleToolsCall(paramsJSON json.RawMessage) (toolCallResult, error) { + var params toolCallParams + if err := json.Unmarshal(paramsJSON, ¶ms); err != nil { + return toolCallResult{}, fmt.Errorf("invalid tool call params: %w", err) + } + + switch params.Name { + case "ship_help": + return s.callShipHelp(params.Arguments) + case "docs_search": + return s.callDocsSearch(params.Arguments) + case "docs_get": + return s.callDocsGet(params.Arguments) + default: + return toolCallResult{}, fmt.Errorf("unknown tool: %s", params.Name) + } +} + +func (s *mcpServer) callShipHelp(arguments json.RawMessage) (toolCallResult, error) { + var in struct { + Topic string `json:"topic"` + } + if len(arguments) > 0 { + if err := json.Unmarshal(arguments, &in); err != nil { + return toolCallResult{}, fmt.Errorf("invalid ship_help arguments: %w", err) + } + } + + text := shipHelpByTopic(strings.TrimSpace(strings.ToLower(in.Topic))) + return toolCallResult{Content: []toolContent{{Type: "text", Text: text}}}, nil +} + +func (s *mcpServer) callDocsGet(arguments json.RawMessage) (toolCallResult, error) { + var in struct { + Path string `json:"path"` + } + if err := json.Unmarshal(arguments, &in); err != nil { + return toolCallResult{}, fmt.Errorf("invalid docs_get arguments: %w", err) + } + if strings.TrimSpace(in.Path) == "" { + return toolCallResult{}, errors.New("docs_get path is required") + } + + absPath, relPath, err := resolveDocPath(s.docsRoot, in.Path) + if err != nil { + return toolCallResult{}, err + } + + body, err := os.ReadFile(absPath) + if err != nil { + return toolCallResult{}, fmt.Errorf("read %s: %w", relPath, err) + } + + text := string(body) + const maxChars = 24000 + if len(text) > maxChars { + text = text[:maxChars] + "\n\n[truncated]" + } + + return toolCallResult{Content: []toolContent{{ + Type: "text", + Text: fmt.Sprintf("# %s\n\n%s", relPath, text), + }}}, nil +} + +func (s *mcpServer) callDocsSearch(arguments json.RawMessage) (toolCallResult, error) { + var in struct { + Query string `json:"query"` + Limit int `json:"limit"` + } + if err := json.Unmarshal(arguments, &in); err != nil { + return toolCallResult{}, fmt.Errorf("invalid docs_search arguments: %w", err) + } + + query := strings.TrimSpace(in.Query) + if query == "" { + return toolCallResult{}, errors.New("docs_search query is required") + } + + limit := in.Limit + if limit <= 0 { + limit = 20 + } + if limit > 50 { + limit = 50 + } + + matches, err := searchDocs(s.docsRoot, query, limit) + if err != nil { + return toolCallResult{}, err + } + if len(matches) == 0 { + return toolCallResult{Content: []toolContent{{Type: "text", Text: "No matches."}}}, nil + } + + var b strings.Builder + fmt.Fprintf(&b, "Matches for %q:\n\n", query) + for _, m := range matches { + fmt.Fprintf(&b, "- %s:%d %s\n", m.Path, m.Line, m.Text) + } + return toolCallResult{Content: []toolContent{{Type: "text", Text: b.String()}}}, nil +} + +func shipHelpByTopic(topic string) string { + switch topic { + case "dev": + return "ship dev commands:\n ship dev\n ship dev worker\n ship dev all\n ship dev --worker\n ship dev --all" + case "test": + return "ship test commands:\n ship test\n ship test --integration" + case "db": + return "ship db commands:\n ship db create\n ship db migrate\n ship db rollback [amount]\n ship db seed" + default: + return "ship - GoShip CLI\n\nUsage:\n ship dev [worker|all] [--worker|--all]\n ship test [--integration]\n ship db " + } +} + +type searchMatch struct { + Path string + Line int + Text string +} + +func searchDocs(docsRoot, query string, limit int) ([]searchMatch, error) { + query = strings.ToLower(query) + matches := make([]searchMatch, 0, limit) + + err := filepath.WalkDir(docsRoot, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if filepath.Ext(path) != ".md" { + return nil + } + + rel, err := filepath.Rel(docsRoot, path) + if err != nil { + return err + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + s := bufio.NewScanner(f) + lineNo := 0 + for s.Scan() { + lineNo++ + line := s.Text() + if strings.Contains(strings.ToLower(line), query) { + matches = append(matches, searchMatch{ + Path: filepath.ToSlash(rel), + Line: lineNo, + Text: strings.TrimSpace(line), + }) + if len(matches) >= limit { + return ioEOFStop + } + } + } + if err := s.Err(); err != nil { + return err + } + return nil + }) + + if err != nil && !errors.Is(err, ioEOFStop) { + return nil, err + } + return matches, nil +} + +var ioEOFStop = errors.New("stop walk") + +func resolveDocPath(docsRoot, input string) (absPath, relPath string, err error) { + p := strings.TrimSpace(filepath.ToSlash(input)) + p = strings.TrimPrefix(p, "docs/") + if p == "" { + return "", "", errors.New("path is empty") + } + if strings.HasPrefix(p, "../") || strings.Contains(p, "/../") { + return "", "", errors.New("path must stay within docs/") + } + + clean := filepath.Clean(filepath.FromSlash(p)) + if clean == "." || clean == "" { + return "", "", errors.New("path is empty") + } + if strings.HasPrefix(clean, "..") || filepath.IsAbs(clean) { + return "", "", errors.New("path must stay within docs/") + } + + absRoot, err := filepath.Abs(docsRoot) + if err != nil { + return "", "", err + } + + abs := filepath.Join(absRoot, clean) + abs = filepath.Clean(abs) + if !strings.HasPrefix(abs, absRoot+string(filepath.Separator)) && abs != absRoot { + return "", "", errors.New("path must stay within docs/") + } + + rel := filepath.ToSlash(clean) + if filepath.Ext(rel) == "" { + rel += ".md" + abs += ".md" + } + + return abs, rel, nil +} + +func toInt(v any, def int) int { + switch t := v.(type) { + case int: + return t + case float64: + return int(t) + case string: + n, err := strconv.Atoi(t) + if err == nil { + return n + } + } + return def +} diff --git a/mcp/ship/internal/server/tools_test.go b/mcp/ship/internal/server/tools_test.go new file mode 100644 index 00000000..1f5816a0 --- /dev/null +++ b/mcp/ship/internal/server/tools_test.go @@ -0,0 +1,130 @@ +package server + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestResolveDocPath(t *testing.T) { + t.Parallel() + + root := t.TempDir() + tests := []struct { + name string + input string + wantRel string + wantFail bool + }{ + {name: "simple file", input: "architecture/01-architecture.md", wantRel: "architecture/01-architecture.md"}, + {name: "without extension", input: "reference/01-cli", wantRel: "reference/01-cli.md"}, + {name: "with docs prefix", input: "docs/00-index.md", wantRel: "00-index.md"}, + {name: "parent traversal", input: "../secret", wantFail: true}, + {name: "nested parent traversal", input: "architecture/../../secret", wantFail: true}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + _, gotRel, err := resolveDocPath(root, tc.input) + if tc.wantFail { + if err == nil { + t.Fatalf("expected error for %q", tc.input) + } + return + } + if err != nil { + t.Fatalf("resolveDocPath(%q) error: %v", tc.input, err) + } + if gotRel != tc.wantRel { + t.Fatalf("resolveDocPath(%q) rel = %q, want %q", tc.input, gotRel, tc.wantRel) + } + }) + } +} + +func TestSearchDocs(t *testing.T) { + t.Parallel() + + docsRoot := t.TempDir() + if err := os.MkdirAll(filepath.Join(docsRoot, "architecture"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(docsRoot, "00-index.md"), []byte("GoShip docs index\nShip CLI"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(docsRoot, "architecture", "01-architecture.md"), []byte("Runtime architecture\nship worker"), 0o644); err != nil { + t.Fatal(err) + } + + matches, err := searchDocs(docsRoot, "ship", 10) + if err != nil { + t.Fatalf("searchDocs error: %v", err) + } + if len(matches) < 2 { + t.Fatalf("expected at least 2 matches, got %d", len(matches)) + } +} + +func TestHandleToolsCall(t *testing.T) { + t.Parallel() + + docsRoot := t.TempDir() + if err := os.MkdirAll(filepath.Join(docsRoot, "reference"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(docsRoot, "reference", "01-cli.md"), []byte("ship dev\nship test"), 0o644); err != nil { + t.Fatal(err) + } + + s := &mcpServer{docsRoot: docsRoot} + + tests := []struct { + name string + method string + args any + wantText string + wantErr bool + }{ + {name: "ship_help general", method: "ship_help", args: map[string]any{"topic": "general"}, wantText: "ship - GoShip CLI"}, + {name: "docs_get", method: "docs_get", args: map[string]any{"path": "reference/01-cli.md"}, wantText: "# reference/01-cli.md"}, + {name: "docs_search", method: "docs_search", args: map[string]any{"query": "ship", "limit": 5}, wantText: "Matches for \"ship\""}, + {name: "unknown", method: "nope", args: map[string]any{}, wantErr: true}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + argsJSON, err := json.Marshal(tc.args) + if err != nil { + t.Fatal(err) + } + paramsJSON, err := json.Marshal(toolCallParams{Name: tc.method, Arguments: argsJSON}) + if err != nil { + t.Fatal(err) + } + + res, err := s.handleToolsCall(paramsJSON) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error") + } + return + } + if err != nil { + t.Fatalf("handleToolsCall error: %v", err) + } + if len(res.Content) == 0 { + t.Fatalf("expected content") + } + if !strings.Contains(res.Content[0].Text, tc.wantText) { + t.Fatalf("response %q does not contain %q", res.Content[0].Text, tc.wantText) + } + }) + } +} From e0ae367c9bd2ce71cc817c9477f5effb73e7e3fb Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 19:55:04 -0800 Subject: [PATCH 013/291] docs: add docs-first playbook and auto-generate LLM.txt --- LLM.txt | 2537 +++++++++++++++++++++++++ Makefile | 4 + docs/00-index.md | 1 + docs/guides/03-how-to-playbook.md | 45 + docs/reference/02-mcp.md | 8 + docs/roadmap/01-framework-plan.md | 26 + lefthook.yml | 3 +- scripts/generate-llm-txt.sh | 53 + scripts/precommit-generate-llm-txt.sh | 6 + 9 files changed, 2682 insertions(+), 1 deletion(-) create mode 100644 LLM.txt create mode 100644 docs/guides/03-how-to-playbook.md create mode 100755 scripts/generate-llm-txt.sh create mode 100755 scripts/precommit-generate-llm-txt.sh diff --git a/LLM.txt b/LLM.txt new file mode 100644 index 00000000..702dbdf9 --- /dev/null +++ b/LLM.txt @@ -0,0 +1,2537 @@ +# LLM.txt + +Generated file. Do not edit manually. +Source of truth is the markdown docs in this repository. + +--- +FILE: README.md +--- +## GoShip: Ship in Record Time ⛵️🛟⚓️📦 + +### A Go + HTMX boilerplate with all the essentials for your SaaS, AI tools, or web apps. Start earning online quickly without the hassle. + +🎯 **The goal of this project** is to build the most comprehensive Go-centric OSS starter boilerplate to ship projects fast. + + +[![Test](https://github.com/leomorpho/GoShip/actions/workflows/test.yml/badge.svg)](https://github.com/leomorpho/GoShip/actions/workflows/test.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + + +

    Logo

    + +

    + Rapid walktrough of project +

    + +

    Check out the video above for a rapid walkthrough of the project! 🏂

    + +This started as a fork of [pagoda](https://github.com/mikestefanello/pagoda), for which I am super grateful! Big shoutout to Mike Stefanello and team! +

    Logo

    + +### Getting Started + +Make sure you have `make` and Golang installed on your machine. + +To get up and running with GoShip: +```bash +# The below command will: +# - set up the postgres/redis/mailer containers +# - build the JS/CSS assets +# - seed the DB with test users +# - start the project in watch mode +make init + +# Running init will fully scrap your state and start with fresh new containers. +# After running `make init` the first time, just use the below for everyday work. +make watch +``` + +For in-depth info on the architecture of the project, please see the [mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) repo. There are some key differences, but since this was originally a fork, 99% of it still applies. I am working on creating clear and actionable documentation, but that is quite time-consuming, so don't hold your socks. + +### Motivation + +Build the same rich interfaces you would build with Javascript frameworks, but with HTML and Go. Limit the number of tools you use. Develop rapidly. + +#### Why the Hell Do We Need Another Boilerplate? + +Well, I noticed that there were none for Go. Now, I know most Go folks like to build it all themselves. And while I love doing that myself, I have many project ideas for which I just want to build that specific project, not the entire infra surrounding it, like auth, notifications, payments, file uploads etc. This project has served me well in bringing to production many projects so far. It has evolved far beyond what I originally planned for, though there is still so much potentional to expand on and implement for. + +If you'd like a no-nonesense (or not too much?) starter kit to get your next project to production ASAP, while also using awesome technologies like Go, you've found a suitable starting point! + +> **Warning alert!** this project is in active development as I am adding things after first trying them out in prod for [Chérie](https://cherie.chatbond.app/), a relationship app to grow your couple. Note that I would welcome any help to develop this boilerplate ❤️. + +### Features && Tech Stack + +See [goship.run](https://goship.run). + +--- + +# Documentation (WIP) + +## File Structure +```bash +|-- cmd +| |-- web # Web server +| |-- worker # Async worker +| |-- seed # Seeder +|-- config # Config files where the non-secret config vars are stored and the config go struct is defined +|-- pkg # Package imports +| |-- context # Context package to handle context across the app +| |-- controller # Controller package to handle requests and responses +| |-- domain # Domain objects that are used throughout the app, these should be specific to your app/project +| |-- funcmap # Custom template functions +| |-- htmx # HTMX lifecycle helpers +| |-- middleware # Middleware for the app +| |-- repos # Repositories +| |-- routes # Think of these as the controllers in a traditional MVC framework +| |-- services # Services on the Container struct +| |-- tasks # Task definitions +| |-- tests # Utility functions for testing +| |-- types # Struct types +|-- templates # HTML templates +|-- ent # Ent ORM, contains the schema for the DB as well as the generated code from the schema. Always commit this to git. + +# Everything else is not Go-specific +|-- deploy.yml # Kamal deployment file +|-- docker-compose.yml # Docker compose file for running the project locally with Docker Desktop +|-- e2e_tests # Playwright E2E tests +|-- scripts # Useful scripts +|-- static # Static files +|-- javascript # Any javascript app can be dropped here. JS and CSS will be built and bundled into a single file. It is currently set up solely for Vanilla JS and Svelte. +|-- build.mjs # Build script for the JS defined in `./javascript` +|-- .env # Secret environment variables +|-- .kamal # Kamal hooks you can use to run commands in the project during deployment +|-- .github # Github actions and secrets +|-- .gitignore # Files to ignore when committing +|-- tailwind.config.js # Tailwind config +|-- tsconfig.json # Typescript config +|-- Procfile # Defines the commands to run the project in watch mode +|-- service-worker.js # Service worker for the PWA +|-- pwabuilder-ios-wrapper # PWA iOS wrapper. Use as a guide for push notifications. +``` + +## Makefile + +The Makefile is the main entry point for the project. It is used to build the project, run the project, and deploy the project. + +The following commands are the most useful ones: +```bash +make init # Initializes the project +make watch # Runs the project in watch mode, rebuilding assets as you go (JS, CSS, Templ, etc) +make test # Runs the tests +make e2eui # Runs the interactive e2e tests with Playwright # +make cover # Shows a Go coverage report of the tests + +# DB specific commands +make ent-new name=YourModelName # Creates a new ent schema file +make makemigrations # Creates a new migration file +make ent-gen # Generates the ent code from the schema +make migrate # Applies migrations +make inspecterd # Shows you a view of all your tables in a UI +make schema + +# Docker commands +make up # Starts the docker containers +make down # Stops the docker containers +make down-volume # Stops the docker containers and removes the volumes +make reset # Stops the docker containers and removes the volumes, then rebuilds the docker containers + +# Assets +make build-js # Builds the JS assets +make watch-js # Watches the JS assets and rebuilds them on change +make build-css # Builds the CSS assets +make watch-css # Watches the CSS assets and rebuilds them on change + +# Worker commands +make worker # Starts the worker +make worker-ui # Will open the terminal to the asynq worker UI + +# Stripe (payments) +make stripe-webhook # Sets up a webhook for stripe for local testing + +make help # Shows all the commands you can run +``` + +## General Architecture + +For in-depth info on the architecture of the project, please see the [mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) repo. There are some key differences, but since this was originally a fork, 99% of it still applies. + +The most important aspects to note are: +- The `Container` struct is instantiated when the app starts up and is used to pass dependencies around the app, specifically core services like `Logger`, `Database`, `ORM`, `Cache`, etc. +- Routes are defined in `app/goship/web/routes/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/pages/` that represents the view. + +## Database + +The current options are: +- Standalone Postgres DB (which you can host anywhere, including locally with Docker) + - For free deployments, see [Supabase](https://supabase.com/pricing) or [Neon](https://neon.tech/pricing). There are also other free options available, and if you host each one of your projects on a different DB, you can use the free tier for all your projects! +- Embedded SQLite DB (which is great for small projects and local development) + + +## Starting DB State + +To get a look at what tables are available to start off, you can run + +```bash +make schema +``` + +or go to `ent/schema` and see the declared schemas. Note that ent generates a lot of code. Do not remove it from git. In fact, make sure to keep it there. + +To create a new schema, do: +```bash +make ent-new name=YourSchemaName +``` + +Then generate the migrations +```bash +make makemigrations +``` + +Then generate the ent generated code to interact with your new schema in Go: +```bash +make ent-gen +``` + +To apply the migrations, either run `make migrate` or do a `make reset` to start from scratch (often times easier, and your test DB should be treated as disposable). + + +## Add a route + +Create a new file in `routes/` and add your route. A route is a standard Echo handler with some added goodies. Once you've added handlers for your route, you can hook it up to the router in `routes/routes.go`, where the route should be registered to be reachable from the web. + +## Set Action Messages + +Following an action (POST/DELETE/GET/etc), a msg can be shown to the user. For example, a success message can shown with `msg.Success("An email confirmation was sent!")` upon user registration. The following message types are currently available: +- success +- info +- warning +- danger + +See `pkg/repos/msg/msg.go` for more info. + +## Realtime and Notifications + +There is a `realtime` route that is setup to handle SSE connections to any client desiring real-time data. Realtime data is sent in "notifications" which are just custom events with a notification type, some data, and a profile id. The `NotifierRepo` handles subscribing the client to the right channels and pushing new notifications to the client. Notifications can be stored in the DB in case the client is offline and needs to be picked up later when they reconnect - these will be shown in the notification center UI. + +Methods for interacting with notifications: + +- `PublishNotification` to send a notification to a user. This can optionally store the notification in the DB. +- `MarkNotificationUnread` to mark a notification as unread. +- `MarkNotificationRead` to mark a notification as read. +- `DeleteNotification` to delete a notification. +- `GetNotifications` to get all notifications for a user. + +Note that actual storage of notifications in the DB is handled by `NotificationStorageRepo`. + +## Notification Permissions + +The `NotificationSendPermissionRepo` handles the permission logic for sending notifications to a user. It is used to determine if a user has granted permission to send notifications to them and lives at `pkg/repos/notifierrepo/permissions.go`. +You can mostly leave this alone, but if you need to add a new permission platform (e.g. a new push notification service), you may need to add a new permission here. + +## Planned Notifications + +The `PlannedNotificationsRepo` handles the logic for sending notifications at a planned time and lives at `pkg/repos/notifierrepo/planned_notifications.go`. The repo does not send any notifications, but rather sets up the DB storage for scheduled notifications. It also contains a method to clean up old notifications. But both the sending and deletion methods need to be called as tasks. Two examples are the `TypeAllDailyConvoNotifications` and `TypeEmailUpdates` tasks, as well as the `TypeDeleteStaleNotifications` task, which are commented out in the `cmd/web/main.go` file. + +The algorithm used to determine best time to send notifications is very primitive. Feel free to improve it! (or I will eventually, though it's low priority) + +## PWA Notifications + +There are 2 push notification repos for different use cases: +- `PwaPushNotificationsRepo`: for sending push notifications to PWAs. +- `FcmPushNotificationsRepo`: for sending push notifications to native Android and iOS apps. + +Both have similar interfaces: +- `AddPushSubscription`: to add a new push subscription, triggered when the profile turns on PWA notifications in their profile settings. +- `SendPushNotifications`: to send a push notification to a user. This is generally handled by the `NotifierRepo` after storing a notification in the DB using the `PublishNotification` method. +- `DeletePushSubscriptionByEndpoint`: to delete a push subscription by endpoint. + +## Profile Repo + +The `ProfileRepo` handles all the profile logic and lives at `pkg/repos/profilerepo/profilerepo.go`. It contains basic CRUD methods for profiles, as well as some helper methods for getting friends, updating profile info, etc. + +There is extensive "friendship" logic in the repo, which is currently not used in the app. It is left over from [Chérie](https://cherie.chatbond.app/) as a demo. Feel free to delete these methods if you don't need them! + +- `GetFriends`: to get all friends for a profile. This is a demo as there is no friends feature in the app. +- `AreProfilesFriends`: to check if two profiles are friends. This is a demo as there is no friends feature in the app. +- `LinkProfilesAsFriends`: to link two profiles as friends. This is a demo as there is no friends feature in the app. +- `UnlinkProfilesAsFriends`: to unlink two profiles as friends. This is a demo as there is no friends feature in the app. +- `GetProfileByID`: to get a profile by ID. +- `GetCountOfUnseenNotifications`: to get the count of unseen notifications for a profile. +- `GetPhotosByProfileByID`: to get the photos for a profile by ID. +- `GetProfilePhotoThumbnailURL`: to get the thumbnail URL for a profile's photo by ID. +- `SetProfilePhoto`: to set the profile photo for a profile by ID. +- `UploadPhoto`: to upload a photo for a profile by ID. +- `UploadImageSizes`: to upload image sizes for a photo by ID. +- `DeletePhoto`: to delete a photo by ID. +- `DeleteUserData`: to delete a user's data by ID. This should be updated to delete all new models that may not cascade delete and is used in the settings to delete a user's data and account. +- `IsProfileFullyOnboarded`: to check if a profile is fully onboarded. This is used in the onboarding flow to check if the profile has completed the onboarding process. Edit as needed. On startup, a non-onboarded profile is redirected to the onboarding page. + +Note that a method `EntProfileToDomainObject` is used to convert the ent profile object to a domain profile object, which is a more generic object that is used throughout the app. Generally, domain objects are preferred over ent objects as they are more generic and are not tied to a specific ORM. + + +## File Uploads + +The `StorageClient` handles all the file storage logic and lives at `pkg/repos/storage/storagerepo.go`. It uses minio under the hood to handle the file uploads with AWS S3 API, which means you can easily swap out the storage backend to any S3-compatible service. + +The following methods are available: +- `CreateBucket`: to create a new bucket. +- `UploadFile`: to upload a new file. +- `DeleteFile`: to delete a file. +- `GetPresignedURL`: to get a presigned URL for a file. +- `GetImageObjectFromFile`: to get an image object from a file. +- `GetImageObjectsFromFiles`: to get image objects from a list of files. + +## Paid/Free Subscriptions + +The `SubscriptionsRepo` handles the subscription logic and lives at `pkg/repos/subscriptions/subscriptions.go`. It uses Stripe under the hood to handle the subscription logic. If you'd like to see the stripe webhooks, they live at `pkg/routes/payments.go`. + +**Note:** currently, the only type of subscription implemented is a monthly subscription that is either paid or free. Feel free to expand on this! + +The following methods are available: +- `CreateSubscription`: to create a new subscription. +- `DeactivateExpiredSubscriptions`: to deactivate all expired monthly subscriptions. +- `UpdateToPaidPro`: to update a subscription to the pro plan. +- `UpdateToFree`: to update a subscription to the free plan. +- `GetCurrentlyActiveProduct`: to get the currently active product for a profile. +- `CancelWithGracePeriod`: to cancel a subscription with a grace period. +- `CancelOrRenew`: to cancel a subscription or renew it. + + +## Regenerate Logo Image Assets + +There is a python script in `scripts/regen_logo_images.py` that should be run when the logo in `static/logo.png` is updated. +This will regenerate the logo assets for different app icons and the favicon. It will also regenerate the correct iOS and Android app icons and place them in the `static/ios-wrapper/` and `static/android-wrapper/` directories. Note that for iOS it will remove alpha transparency and make the background black (as apple requires). + +```bash +cd scripts +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt + +python3 scripts/regen_logo_images.py +``` + +## Run Tasks + +Currently, tasks are run using [asynq](https://github.com/hibiken/asynq). This unfortunately requires [redis](https://redis.io/) to be running. This can make deployment a bit trickier as it means you will need at least 3 VPS with Kamal (except if I'm missing something), as you will need one for the web app, one for the worker, and one for the cache/queue. This is far from ideal for small projects, and [pagoda](https://github.com/mikestefanello/pagoda)'s author decided to use [backlite](https://github.com/mikestefanello/backlite), a tool he created to use SQLite as the task queue. I have not gone around to pulling these changes in yet, and I am hesitant at this point as I have multiple projects running in prod, and only 1 VPS running a cache that is serving all my projects...which means that I don't have a huge incentive to add this in. + +If you'd like to change asynq to backlite, you can refer [to this pagoda PR](https://github.com/mikestefanello/pagoda/pull/72/files) to bring the changes in your goship instance. + +## Drop in any JS App + +While the project primarily uses HTMX, it also supports integrating JavaScript applications. The current build process creates two separate bundles: +1. A single Vanilla JavaScript bundle +2. A single Svelte bundle +This approach allows you to incorporate JavaScript functionality alongside the HTMX-driven parts of your application. Here's how it works: +- The build.mjs script handles the bundling process for both Vanilla JS and Svelte components. +-Each framework (Vanilla JS and Svelte) is compiled into its own single file bundle. +-These bundles can be served to the frontend and used where needed in your application. + +**Note:** While this method allows for easy integration, it does come with the trade-off of potentially large bundle sizes. Future improvements could involve optimizing the build process to create smaller, component-specific bundles for more efficient loading. + +This setup provides flexibility to use JavaScript frameworks alongside HTMX, leveraging the strengths of both approaches in different parts of your application. + +Note that any JS framework could be used. + +**Note:** Svelte is used for highly interactive components, although I've come to regret this as it is a large framework to bundle and slow down the initial page load. In the future, I plan to remove Svelte and only use HTMX for all components. This would not impact the ability to drop in any JS app, however. + +## Playwright E2E Tests + +TODO: the test file can be found at `e2e_tests/tests/goship.spec.ts` and is currently still the one from [chérie](https://cherie.chatbond.app/)...I will update it soon! + +You can run the Playwright tests with: +```bash +make e2eui +``` + +NOTE: on older/slower machines, the tests may time out. If so, you can increase the timeout in the test file. I was facing that issue when testing locally on a 2014 Macbook Pro, though have not faced it since running the tests on my M2 Mac. I am no playwright expert too, so perhaps I am missing something. + +## Deployment + +I currently only use Kamal for deployment. Should you want to contribute in adding other deployment methods, please create a subdirectory in `deploy` and add it there, so that it's well organized. + +### Kamal + +First, make sure all your env vars in the Kamal file `deploy/kamal/deploy.yml` are correct. All your vars should be set either in: + +- `config/config.yml`: only non-secret ones +- `deploy/kamal/deploy.yml`: only non-secret ones +- `.env`: all secret vars + +Then, set the IP of the server host in `deploy/kamal/deploy.yml`, as well as your image and registry details. Read up on the [kamal documentation](https://kamal-deploy.org) if you get stuck anywhere here. + +### Set up live server + +The below command will install docker, build your image, push it to your registry, and then pull it on your remote VPS. If you set up any accessory (cache, standalone DB that is not hosted etc), these will also be deployed. + +```bash +kamal setup -c deploy/kamal/deploy.yml +``` + +At this point, your project should be live, and if `128.0.0.1111` is the IP of your VPS, entering that IP in the search bar on your browser should bring up your site. + +### HTTPS + +Hop into your VPS console. + +```bash +mkdir -p /letsencrypt && touch /letsencrypt/acme.json && chmod 600 /letsencrypt/acme.json +``` + +Then locally, run + +```bash +kamal traefik reboot -c deploy/kamal/deploy.yml +``` + +Your site should now have TLS enabled and you should see the lock icon the search bar. + +For reference, the above procedure was taken from [this Kamal issue](https://github.com/basecamp/kamal/discussions/112). + +### Firewall + +There are some sample firewall scripts in `config/firewalls/` to help you get started. They make use of `ufw` so make sure that is installed on your system. + +The worker firewall should block all ports by default except for SSH and internal network traffic. + +The web app firewall should block all ports by default except for SSH, HTTPS, and internal network traffic. + +The accessories firewall should block all ports by default, though if using Asynq, you should allow 8080 (Asynq UI) to your specific IP. + +## Future Work + +### Environment Management + +Improve the experience with handling config and environment variables. Currently, there is an `.env` file with secrets, which can be of the form `PAGODA_STORAGE_S3ACCESSKEY=123` and then in `config.yml` it is under: +```yml +pagoda: + storage: + s3accesskey: 123 +``` + +And in `config.go` it is defined as: +```go +type Config struct { + Pagoda struct { + Storage struct { + S3accesskey string + } + } +} +``` + +This is fine for simple cases but can quickly be confusing. It would be nice to have a more robust env management system, perhaps one that can auto-generate env vars for you in the `.env` and `config.yml` files, so that no human error is introduced, leading the developer to confusion trying to figure out what is going on. + +### Code generation + +#### Scaffold Generator + +This is a CLI command that will generate a route, model, and view for you. It's just at the idea stage and would be a great feature to have. A lot of time goes into writing boilerplate code for each new route. Ideally, it supports generating templ/htmx routes and JSON routes. + +Example: + +```bash +goship generate scaffold Post title:string content:text +``` + +##### Generated Model: +```go +// ent/schema/post.go +package schema + +type Post struct { + ent.Schema +} + +func (Post) Mixin() []ent.Mixin { + return []ent.Mixin{ + TimeMixin{}, + } +} + +func (Post) Fields() []ent.Field { + return []ent.Field{ + field.String("title"), + field.String("content"), + } +} + +func (UsPoster) Edges() []ent.Edge { + return nil +} +``` + +##### Route +```go +// app/controllers/post_controller.go +package routes + +type postRoute struct {} + +func NewPostRoute( + ctr controller.Controller, +) postRoute { + return postRoute{ + ctr: ctr, + } +} + +func (p *postRoute) Index(ctx echo.Context) { + // List all posts +} + +func (p *postRoute) Show(ctx echo.Context) { + // Show a specific post +} + +func (p *postRoute) New(ctx echo.Context) { + // Render form to create new post +} + +func (p *postRoute) Create(ctx echo.Context) { + // Logic to create a new post +} + +func (p *postRoute) Edit(ctx echo.Context) { + // Render form to edit post +} + +func (p *postRoute) Update(ctx echo.Context) { + // Logic to update a post +} + +func (p *postRoute) Destroy(ctx echo.Context) { + // Logic to delete a post +} +``` + +##### Generated Routes: + +The routes will be automatically added to the router: +```go +postRoute := NewPostRoute(ctr) +g.GET("/posts", postRoute.Index).Name = "posts.index" +g.GET("/posts/:id", postRoute.Show).Name = "posts.show" +g.GET("/posts/new", postRoute.New).Name = "posts.new" +g.POST("/posts", postRoute.Create).Name = "posts.create" +g.GET("/posts/:id/edit", postRoute.Edit).Name = "posts.edit" +g.POST("/posts/:id", postRoute.Update).Name = "posts.update" +g.DELETE("/posts/:id", postRoute.Destroy).Name = "posts.destroy" +``` + +##### Generated views +```go +// app/goship/views/posts.templ +package pages + +import ( + "github.com/mikestefanello/pagoda/pkg/controller" + "github.com/mikestefanello/pagoda/pkg/types" + "github.com/mikestefanello/pagoda/app/goship/views/components" +) + +templ PostsIndex(page *controller.Page) { +} + +templ PostsShow(page *controller.Page) { +} + +templ PostsNew(page *controller.Page) { +} + +templ PostsEdit(page *controller.Page) { +} + +templ PostsCreate(page *controller.Page) { +} + +templ PostsUpdate(page *controller.Page) { +} + +templ PostsDestroy(page *controller.Page) { +} +``` +##### Generate Type Data Struct + +```go +// types/post.go +package types + +type Post struct { + Title string + Content string +} +``` + + +--- +FILE: docs/00-index.md +--- +# Documentation Index + +This `docs/` directory is internal and implementation-focused. +It is not intended as user-facing product documentation. + +## Goals + +- Explain what the project does today based on code, not assumptions. +- Give developers and AI agents a fast map of where to make changes. +- Capture system risks and incomplete areas so work is directed intentionally. + +## README Index + +1. `README-01` - `../README.md`: project-level overview and onboarding. +2. `README-02` - `00-index.md`: documentation hub and map of all internal docs. + +## Structure + +### Architecture + +1. `A01` - `architecture/01-architecture.md`: runtime architecture, request flow, and service composition. +2. `A02` - `architecture/02-structure-and-boundaries.md`: canonical placement rules for app vs framework code. +3. `A03` - `architecture/03-project-scope-analysis.md`: end-to-end feature and capability analysis. +4. `A04` - `architecture/04-http-routes.md`: route inventory grouped by access level and purpose. +5. `A05` - `architecture/05-data-model.md`: Ent entities and domain model coverage. +6. `A06` - `architecture/06-known-gaps-and-risks.md`: confirmed implementation gaps and technical risks. + +### Guides + +1. `G01` - `guides/01-ai-agent-guide.md`: practical guide for AI agents working in this repo. +2. `G02` - `guides/02-development-workflows.md`: day-to-day run/build/test/migration workflows. +3. `G03` - `guides/03-how-to-playbook.md`: prioritized how-to guide backlog and writing template. + +### Reference + +1. `R01` - `reference/01-cli.md`: living CLI specification (`ship`) for developers and agents. +2. `R02` - `reference/02-mcp.md`: living MCP spec (`ship-mcp`) for LLM-facing docs and CLI support. + +### Policies + +1. `P01` - `policies/01-engineering-standards.md`: baseline requirements for maintainable repositories (hooks, CI, tests, docs, versioning). + +### Roadmap + +1. `M01` - `roadmap/01-framework-plan.md`: long-term framework strategy and execution tracker. + +## Primary Source Files Used For This Analysis + +- `cmd/web/main.go` +- `cmd/worker/main.go` +- `cmd/seed/main.go` +- `cli/ship/cmd/ship/main.go` +- `cli/ship/cli.go` +- `pkg/services/container.go` +- `app/goship/web/routes/router.go` +- `app/goship/web/routes/*.go` +- `pkg/tasks/*.go` +- `pkg/repos/**/*.go` +- `ent/schema/*.go` +- `config/config.go` +- `config/config.yaml` +- `Makefile` +- `build.mjs` +- `package.json` +- `e2e_tests/tests/goship.spec.ts` + +--- +FILE: docs/architecture/01-architecture.md +--- +# Architecture + +## High-Level Layout + +The application follows a layered structure: + +- `cmd/*`: process entrypoints (`web`, `worker`, `seed`) +- `pkg/services`: dependency container and infrastructure clients +- `app/goship/web/routes`: HTTP handlers and route composition +- `pkg/middleware`: auth/session/cache/onboarding/request middleware +- `pkg/repos`: data access and external service adapters +- `pkg/controller`: rendering, page object, redirect helpers +- `app/goship/views`: Templ UI components/layouts/pages/emails +- `ent`: schema + generated ORM +- `pkg/tasks`: Asynq task processors + +## Web Runtime Flow + +1. `cmd/web/main.go` creates container via `services.NewContainer()`. +2. `routes.BuildRouter(c)` configures middleware stack, registers routes, and returns an error on startup misconfiguration. +3. Echo server starts with request timeout middleware (SSE-aware). +4. Request path executes middleware chain, route handler, and page rendering. + +## Worker Runtime Flow + +1. `cmd/worker/main.go` loads config and starts Asynq server. +2. Creates app container and builds router (for reverse route URLs in tasks). +3. Constructs repo instances needed by task processors. +4. Registers handlers on Asynq mux and runs worker. + +## Container Composition + +`pkg/services/container.go` is the core composition root. + +Currently initialized in `NewContainer()`: + +- Config +- Validator +- Echo web server + logger +- DB connection +- Ent ORM +- Auth client +- Mail client +- Stripe API key setup + +Not currently initialized (commented out): + +- Cache client +- Notifier repo +- Task client + +This mismatch affects parts of the runtime that assume those dependencies exist. See `known-gaps-and-risks.md`. + +## HTTP Middleware Stack + +Primary middleware set in `app/goship/web/routes/router.go` includes: + +- Trailing slash normalization +- Panic recovery +- Security headers +- Request ID +- Gzip +- Structured request logging +- Request timeout (SSE skipped) +- Session middleware +- Authenticated user hydration +- CSRF middleware +- Device type tagging + +Additional gatekeepers: + +- `RequireAuthentication` +- `RequireNoAuthentication` +- `RedirectToOnboardingIfNotComplete` +- Password token validity loader + +## Rendering Model + +The UI is server-rendered using Templ components. + +- Base page abstraction: `pkg/controller/page.go` +- Render orchestration: `pkg/controller/controller.go` +- Layout wrappers: `app/goship/views/layouts/*.templ` +- Route page components: `app/goship/views/pages/*.templ` + +HTMX behavior is integrated in the page object (`Page.HTMX`) and controller render logic. + +## Data Layer + +- Ent ORM (`ent`) is authoritative for schema and query generation. +- Schema create/migrate is invoked in app startup via `c.ORM.Schema.Create(...)`. +- Repository packages encapsulate higher-level domain operations. + +## Async + Notifications Architecture + +- Asynq handles background jobs with Redis backend. +- Notification system is designed around: + - persistent DB notifications + - pub/sub events for SSE + - push channels (PWA + FCM) +- SSE endpoint exists (`app/goship/web/routes/realtime.go`) but route wiring is currently disabled. + +## Frontend Asset Architecture + +- `build.mjs` bundles Svelte entrypoints under `javascript/svelte/*.js` +- Also bundles vanilla JS from `javascript/vanilla/main.js` +- Outputs static bundles and meta files in `static/` +- Tailwind build pipeline outputs `static/styles_bundle.css` + +## Deployment/Operations Shape + +- Local process orchestration via Overmind (`Procfile`) +- Docker Compose for Redis + Mailpit in current config +- Kamal deployment files present (`deploy/`, `.kamal/`) + +--- +FILE: docs/architecture/02-structure-and-boundaries.md +--- +# Structure and Boundaries + +This document defines where code belongs as GoShip evolves into a Rails-like framework plus example app. + +## Current Top-Level Shape (Single Repository) + +- `app/goship/`: app-specific code for the first-party GoShip app +- `cmd/`: runnable entrypoints for the app module (`web`, `worker`, `seed`) +- `cli/ship/`: standalone CLI module (`ship`) that lives in this same repository +- `mcp/ship/`: standalone MCP module (`ship-mcp`) for LLM-facing docs/CLI tooling +- `pkg/`: reusable framework-level libraries and adapters +- `config/`: runtime configuration +- `ent/`: schema and generated ORM +- `docs/`: internal design and implementation documentation + +Monorepo note: + +- GoShip currently uses one repository with multiple Go modules: +- root app/framework module + `cli/ship` module + `mcp/ship` module +- `go.work` ties local development across modules together for maintainers. + +## App vs Framework Rules + +Use this placement rule for every new file: + +- Put code in `app/goship/...` when it encodes product behavior/UI for the GoShip app. +- Put code in `pkg/...` when it is reusable as framework infrastructure across apps. + +## Web Layer Layout + +App web code is now app-scoped: + +- `app/goship/web/routes`: route composition + handlers +- `app/goship/views`: templ components/layouts/pages/emails + +Router source of truth: + +- `app/goship/web/routes/router.go` + +## Refactor Status + +Completed in this pass: + +- Moved routes from `pkg/routing/routes` to `app/goship/web/routes`. +- Moved templ views from `templates` to `app/goship/views`. +- Updated imports and test package paths accordingly. + +Still intentionally centralized (next phase): + +- `pkg/repos` +- `pkg/services` + +These remain framework-level until each package is classified as either: + +- app-specific (move under `app/goship/...`), or +- reusable framework module (stay in `pkg/...` or move to future dedicated framework modules). + +--- +FILE: docs/architecture/03-project-scope-analysis.md +--- +# Project Scope Analysis + +## What This Project Is + +GoShip is a Go + Echo + Templ + HTMX starter application that ships with: + +- Session-based authentication and account lifecycle flows +- Profile and onboarding flows +- Email subscriptions and transactional email support +- Subscription billing integration via Stripe +- Notification infrastructure (DB + push + SSE-oriented architecture) +- S3-compatible file storage support with image variants +- Background task processing (Asynq worker) +- Frontend asset bundling for Svelte components and vanilla JS + +The repository still carries heritage from a related product domain ("Cherie"), and some feature areas are partially wired or intentionally disabled. + +## Runtime Programs + +- `cmd/web/main.go`: main HTTP application server +- `cmd/worker/main.go`: asynchronous worker process for task handlers +- `cmd/seed/main.go`: seed runner for test/dev data + +## Feature Areas + +## 1) Authentication and Account Management + +Core flows implemented in routes and services: + +- Login/logout (`app/goship/web/routes/login.go`, `logout.go`) +- Register (`register.go`) +- Forgot/reset password (`forgot_password.go`, `reset_password.go`) +- Email verification (`verify_email.go`) +- Auth middleware and session handling (`pkg/middleware/auth.go`, `pkg/services/auth.go`) + +Key implementation choices: + +- Cookie-backed session auth using Gorilla sessions via Echo middleware. +- Password reset tokens stored as bcrypt hashes. +- Email verification tokens use JWT signed with app encryption key. + +## 2) Onboarding, Preferences, and Profile + +- Onboarding and preferences mostly in `app/goship/web/routes/preferences.go` +- Profile page in `profile.go` +- Mark onboarding completion (`/welcome/finish-onboarding`) +- Profile photo and gallery image routes (`profile_photo.go`, `upload_photo.go`) + +## 3) Payments and Subscription Lifecycle + +- Stripe checkout + customer portal + webhook in `app/goship/web/routes/payments.go` +- Local subscription state managed in `pkg/repos/subscriptions/subscriptions.go` +- Product model currently centered on free vs pro (`pkg/domain/enum.go`) + +Webhook flow currently handles: + +- `customer.subscription.created` +- `customer.subscription.updated` +- `customer.subscription.deleted` + +## 4) Notifications and Realtime Capabilities + +Implemented infrastructure includes: + +- Notification domain and storage +- Notification permissions by platform (push, fcm_push, email, sms) +- PWA and FCM push subscription storage/sending +- SSE pub/sub abstractions + +Status of exposure: + +- Some notification endpoints are active (count endpoint, permission/subscription management) +- Several notification-center routes are currently commented out in router wiring +- SSE route wiring is currently commented out in router (`sseRoutes` not enabled) + +## 5) Email Features + +- Newsletter-style email subscription flow (`email_subscribe.go`, `verify_email_subscription.go`) +- Task processor for subscription confirmation emails (`pkg/tasks/mail.go`) +- Update email sender integration (`emailsmanager`) +- Mail provider abstraction supports SMTP and Resend (`pkg/repos/mailer`) + +## 6) File Storage and Images + +- S3-compatible object storage through MinIO client (`pkg/repos/storage/storagerepo.go`) +- DB metadata persisted in `file_storages` +- Image size variants represented by enums and related image size records +- Signed URLs generated for image access + +## 7) Background Tasks + +Task processors under `pkg/tasks`: + +- Email subscription confirmation +- Email updates +- Subscription deactivation maintenance +- Daily conversation notification orchestration +- Stale notification cleanup + +Worker bootstrap and registration in `cmd/worker/main.go`. + +## 8) Frontend Delivery Model + +- Server-rendered pages via Templ (`app/goship/views/` + `pkg/controller`) +- HTMX-enhanced interactions +- Optional Svelte components bundled into `static/svelte_bundle.js` +- Optional vanilla JS bundle into `static/vanilla_bundle.js` + +Build pipeline: + +- JS via `build.mjs` + esbuild +- CSS via Tailwind CLI in Makefile + +## Environments and Configuration + +Config loading: + +- `config/config.go` + `config/config.yaml` +- Production can override via env vars (Viper env binding) + +Storage modes: + +- Embedded SQLite (default in config) +- Standalone Postgres path exists and includes pgvector extension setup + +## Testing Surface + +- 70+ Go tests in `pkg/**` +- Playwright e2e folder exists (`e2e_tests/`), but specs are currently product-domain stale and marked TODO + +## Operational Tooling + +- `Makefile` is the primary task runner (init, watch, test, migrations, worker) +- `Procfile` for multi-process dev with Overmind +- Docker Compose currently starts Redis + Mailpit; Postgres service is commented out + +## Practical Summary + +This codebase is a strong "production-ready starter" foundation with authentication, payments, notifications, storage, and worker primitives. It is also in an active transitional state where some features are scaffolded but not fully wired in the web runtime. + +--- +FILE: docs/architecture/04-http-routes.md +--- +# HTTP Route Map + +Routes are registered in `app/goship/web/routes/router.go`. + +## Public/General Routes + +- `GET /` landing page +- `GET /up` healthcheck +- `GET /clear-cookie` +- `GET /about` +- `GET /privacy-policy` +- `GET /install-app` + +Docs pages (user-facing in-app docs): + +- `GET /docs` +- `GET /docs/gettingStarted` +- `GET /docs/guidedTour` +- `GET /docs/architecture` + +Email subscription: + +- `GET /emailSubscribe` +- `POST /emailSubscribe` +- `GET /email/subscription/:token` + +Service worker / app-links: + +- `GET /service-worker.js` +- `GET /.well-known/assetlinks.json` + +## User-Not-Authenticated Group (`/user`) + +- `GET /user/login` +- `POST /user/login` +- `GET /user/register` +- `POST /user/register` +- `GET /user/password` +- `POST /user/password` +- `GET /user/password/reset/token/:user/:password_token/:token` +- `POST /user/password/reset/token/:user/:password_token/:token` + +## Authenticated Onboarding Group (`/welcome`) + +- `GET /welcome/preferences` +- `GET /welcome/preferences/phone` +- `GET /welcome/preferences/phone/verification` +- `POST /welcome/preferences/phone/verification` +- `POST /welcome/preferences/phone/save` +- `GET /welcome/preferences/display-name/get` +- `POST /welcome/preferences/display-name/save` +- `GET /welcome/preferences/delete-account` +- `GET /welcome/preferences/delete-account/now` +- `GET /welcome/finish-onboarding` +- `GET /welcome/profileBio` +- `POST /welcome/profileBio/update` + +Notification subscription management during onboarding: + +- `GET /welcome/subscription/push` +- `POST /welcome/subscription/:platform` +- `DELETE /welcome/subscription/:platform` +- `GET /welcome/email-subscription/unsubscribe/:permission/:token` + +## Authenticated Group (`/auth`) + +- `GET /auth/logout` + +Fully onboarded-only routes (`/auth` with onboarding guard): + +- `GET /auth/homeFeed` +- `GET /auth/homeFeed/buttons` +- `GET /auth/profile` +- `GET /auth/uploadPhoto` +- `POST /auth/uploadPhoto` +- `DELETE /auth/uploadPhoto/:image_id` +- `GET /auth/currProfilePhoto` +- `POST /auth/currProfilePhoto` +- `GET /auth/notifications/normalNotificationsCount` +- `GET /auth/payments/get-public-key` +- `POST /auth/payments/create-checkout-session` +- `POST /auth/payments/create-portal-session` +- `GET /auth/payments/pricing` +- `GET /auth/payments/success` + +## Auth-Adjacent Routes + +- `GET /email/verify/:token` + +## External Integration Routes + +- `POST /Q2HBfAY7iid59J1SUN8h1Y3WxJcPWA/payments/webhooks` + +## Development-Only Error Preview Routes + +Registered only when not production: + +- `GET /error/400` +- `GET /error/401` +- `GET /error/403` +- `GET /error/404` +- `GET /error/500` + +## Routes Present But Not Currently Wired + +SSE route function exists but registration is commented out: + +- `GET /auth/realtime` + +Notification center routes have implementations but are commented out in route wiring: + +- list notifications +- mark all read +- delete notification +- mark read/unread endpoints + + +--- +FILE: docs/architecture/05-data-model.md +--- +# Data Model + +Primary schema is defined in `ent/schema/*.go` and compiled into generated Ent code in `ent/`. + +## Core Entities + +Identity and profile: + +- `User` +- `Profile` +- `PasswordToken` +- `LastSeenOnline` + +Communication and notifications: + +- `Notification` +- `NotificationPermission` +- `NotificationTime` +- `PwaPushSubscription` +- `FCMSubscriptions` +- `SentEmail` + +Billing: + +- `MonthlySubscription` + +Media and file storage: + +- `Image` +- `ImageSize` +- `FileStorage` + +Other domain support: + +- `EmailSubscription` +- `EmailSubscriptionType` +- `Invitation` +- `PhoneVerificationCode` +- `Emojis` + +## Important Relationships + +- `User` has one `Profile` (`User.profile` unique edge). +- `Profile` has many `notifications`, `photos`, `invitations`, push subscriptions, and notification permissions. +- `MonthlySubscription` has one `payer` profile and many `benefactors` profiles. +- `Notification` belongs to one `Profile`. +- `NotificationPermission` is unique per `(profile_id, permission, platform)`. +- `NotificationTime` is unique per `(profile_id, type)`. +- `Image` has many `ImageSize` records. +- `ImageSize` has one required `FileStorage` object. + +## Domain Enums + +Defined in `pkg/domain/enum.go`: + +- Notification types +- Notification permission types +- Notification delivery platforms +- Image sizes and categories +- Product type (free/pro) +- Bottom navbar item +- Email subscription list + +## Subscription Model Notes + +Current app logic treats free plan as absence of an active pro subscription. + +- Creation path often creates trial pro subscription on onboarding. +- Active subscription uniqueness is enforced by unique index on `(paying_profile_id, is_active)`. + +## Notification Model Notes + +Notification records include: + +- Type/title/text/link +- Read and read timestamp +- Optional actor/resource linkage +- Per-profile ownership + +Permissions are separated from notifications and keyed by platform + permission type. + +## Storage Model Notes + +`FileStorage` holds object metadata and object key info for S3-compatible storage. +The storage repo generates presigned URLs and maps image sizes into frontend-friendly `domain.Photo` objects. + + +--- +FILE: docs/architecture/06-known-gaps-and-risks.md +--- +# Known Gaps and Risks + +This list is based on direct code inspection and is intended to guide contributor priorities. + +## 1) Container Initialization Mismatch (High) + +In `pkg/services/container.go`, `NewContainer()` does not call: + +- `initCache()` +- `initNotifier()` +- `initTasks()` + +Yet runtime code assumes these dependencies exist in multiple places, and `Shutdown()` calls `c.Tasks.Close()` and `c.Cache.Close()` unconditionally. + +Impact: + +- Potential nil-pointer panics on shutdown or during feature paths that rely on tasks/notifier/cache. +- Web and worker behavior can diverge from intended architecture. + +## 2) Realtime SSE Route Not Wired (High for realtime features) + +`app/goship/web/routes/realtime.go` has a full SSE handler, but router registration is commented out: + +- `sseRoutes(c, s, ctr)` is commented in `app/goship/web/routes/router.go`. + +Impact: + +- Realtime endpoint is effectively disabled in web runtime. + +## 3) Notification Center Endpoints Partially Disabled (Medium) + +Route handlers exist in `app/goship/web/routes/notifications.go`, but several are commented out during route wiring. + +Impact: + +- Notification center behavior is incomplete from an HTTP exposure perspective. +- Some notifier capabilities are not reachable from active routes. + +## 4) Stale/Inconsistent E2E Coverage (Medium) + +`e2e_tests/tests/goship.spec.ts` is marked with TODO and contains stale product/domain assumptions. + +Impact: + +- End-to-end test confidence for current GoShip behavior is limited. + +## 5) Dev Runtime Drift Between Config and Docker Compose (Medium) + +- Default config uses embedded SQLite (`config/config.yaml`). +- Docker Compose currently starts Redis and Mailpit only; DB service is commented out. +- Make targets include Postgres-dependent commands. + +Impact: + +- Contributors can experience confusion about canonical local dev DB path. + +## 6) In-App Docs Are Present But Sparse (Low) + +`/docs/*` pages exist, but architecture/getting-started sections are mostly placeholders. + +Impact: + +- Existing user-facing docs routes do not currently reflect true implementation depth. + +## 7) Some Feature Paths Still Use Placeholder Data (Low) + +Example: home feed button counts are hardcoded in `app/goship/web/routes/home_feed.go`. + +Impact: + +- UI may represent scaffolding rather than production data behavior in some sections. + +## Suggested Priority Order + +1. Fix container/service initialization and safe shutdown semantics. +2. Decide and document realtime strategy: wire SSE or remove dead path. +3. Re-enable or remove notification-center routes consistently. +4. Refresh e2e tests to match current GoShip flows. +5. Align local stack docs with actual DB mode and compose services. + + +--- +FILE: docs/guides/01-ai-agent-guide.md +--- +# AI Agent Guide + +This guide is for code agents making changes in this repository. + +## Start Here + +1. Read `docs/architecture/03-project-scope-analysis.md` and `docs/architecture/06-known-gaps-and-risks.md`. +2. Inspect route wiring in `app/goship/web/routes/router.go` before editing handlers. +3. Inspect `pkg/services/container.go` before assuming a dependency is initialized. + +## Architectural Conventions + +- HTTP handlers live in `app/goship/web/routes`. +- Domain logic should prefer repository packages (`pkg/repos/...`) over route-level DB logic. +- Rendering is typically done via `controller.Page` + templ components. +- Enums/constants are centralized in `pkg/domain`. + +## Safe Change Workflow + +1. Identify layer to change: +- routing +- repository/service +- domain/schema +- template/frontend + +2. Check for related tests: +- `rg "func Test" pkg/...` +- route tests in `app/goship/web/routes/*_test.go` + +3. Implement minimal, local change first. +4. Run targeted tests, then broader tests if needed. +5. Update docs in `docs/` when behavior or architecture changes. + +## Key Files By Concern + +Runtime bootstrap: + +- `cmd/web/main.go` +- `cmd/worker/main.go` +- `cmd/seed/main.go` + +Dependency wiring: + +- `pkg/services/container.go` +- `pkg/services/auth.go` +- `pkg/services/tasks.go` + +Routing and middleware: + +- `app/goship/web/routes/router.go` +- `pkg/middleware/*.go` + +Data and domain: + +- `ent/schema/*.go` +- `pkg/repos/**/*.go` +- `pkg/domain/*.go` + +UI and rendering: + +- `pkg/controller/*.go` +- `app/goship/views/**/*.templ` +- `javascript/**/*` + +## Common Pitfalls + +- Assuming cache/notifier/task clients are initialized in the container. +- Implementing a route but not registering it in `router.go`. +- Adding schema logic without checking migration/generation workflow. +- Updating frontend behavior without checking templ + JS integration points. + +## Commands Commonly Used + +- `make watch` (multi-process local dev) +- `make test` (Go tests) +- `make worker` (async worker) +- `make build-js` +- `make build-css` +- `make ent-gen` +- `make makemigrations name=YourChange` + +## Documentation Rule + +When code behavior changes, update at least: + +- `docs/architecture/03-project-scope-analysis.md` if capability changed +- `docs/architecture/04-http-routes.md` if route surface changed +- `docs/architecture/06-known-gaps-and-risks.md` if a risk was added/removed + +--- +FILE: docs/guides/02-development-workflows.md +--- +# Development Workflows + +## Local Startup + +Primary commands (from `Makefile`): + +- `make init`: reset containers, build assets, seed data, start watch mode +- `make watch`: start process group via Overmind (`Procfile`) + +`Procfile` runs: + +- `watch-js` +- `watch-go` +- `watch-css` +- `watch-go-worker` + +## Services and Infra + +Docker Compose currently provisions: + +- Redis (`goship_cache`) +- Mailpit (`goship_mailpit`) + +Notes: + +- Postgres service is present but commented out in `docker-compose.yml`. +- Default config DB mode is embedded SQLite. + +## Assets + +JS build: + +- `npm run build` (via `build.mjs`) +- Bundles Svelte entrypoints and vanilla JS + +CSS build: + +- Tailwind CLI to `static/styles_bundle.css` + +## Database and Schema + +Entity schema source: + +- `ent/schema/*.go` + +Common workflow: + +1. `make ent-new name=YourEntity` (if new entity) +2. `make makemigrations name=your_change` +3. `make ent-gen` +4. `make migrate` + +## Worker and Tasks + +Run worker manually: + +- `make worker` + +Asynq UI: + +- `make workerui` + +Task processor registration: + +- `cmd/worker/main.go` + +## Testing + +Go tests: + +- `make test` +- `make cover` + +E2E tests: + +- `make e2e` +- `make e2eui` + +Note: current e2e specs are partially stale and should be treated as non-authoritative for GoShip behavior. + + +--- +FILE: docs/guides/03-how-to-playbook.md +--- +# How-To Playbook (Docs-First) + +This file tracks practical how-to guides we want to provide for GoShip. + +## Objective + +Create docs quality equal to or better than Pagoda's onboarding experience, with concrete implementation guides for common tasks. + +## Priority Guides + +1. Add a new endpoint (route + handler + templ view + tests). +2. Add a new page with server-rendered UI. +3. Add a new Ent model and migration flow. +4. Add a new service/repository with boundaries (app vs framework placement). +5. Add a background job and wire worker behavior. +6. Add a module adapter (db/cache/jobs/pubsub/storage) with interface wiring. +7. Add realtime event flow (publish + subscribe + UI update). +8. Add authentication-protected endpoint and authorization check. +9. Add table-driven unit tests for routes/repos/services. +10. Add integration test (happy path only, Docker-minimal). + +## Guide Template (Use For Every How-To) + +1. Goal: one-sentence desired outcome. +2. Preconditions: files, commands, env vars needed. +3. Steps: exact edits/commands in order. +4. Validation: tests/commands and expected output. +5. Common failures: top 3 mistakes and fixes. +6. References: links to canonical docs and source files. + +## Writing Rules + +1. Prefer copy-pastable commands and exact file paths. +2. Use current numbered docs paths in all references. +3. Keep examples aligned with `ship` commands where available. +4. Every guide must include a test/verification section. +5. Keep sections short and task-oriented; avoid long conceptual digressions. + +## Execution TODO + +- [ ] Draft guide: Add a new endpoint. +- [ ] Draft guide: Add a new Ent model and migration. +- [ ] Draft guide: Add a background job. +- [ ] Draft guide: Add tests (table-driven + integration). +- [ ] Draft guide: Add a module adapter. + +--- +FILE: docs/policies/01-engineering-standards.md +--- +# Repository Requirements (Engineering Standards) + +This file defines baseline requirements for any GoShip repository (framework, CLI, or app) to stay maintainable. + +## 1) Repository Layout + +1. Keep runtime/app code, framework code, and CLI code clearly separated. +2. Keep architecture and operational docs in `docs/`. +3. Keep one living plan document for roadmap/decisions. + +## 2) Local Developer Workflow + +Required commands must exist and work: + +1. `make dev` (web-only default) +2. `make test` (fast Docker-free unit set) +3. `make test-integration` (infra-backed integration set) +4. `make testall` (unit + integration) + +If CLI exists, equivalent commands must exist in CLI: + +1. `ship dev` +2. `ship test` +3. `ship test --integration` + +## 3) Pre-Commit Hooks (Required) + +Use `lefthook` with at least: + +1. unit test package set +2. formatting checks for touched languages +3. basic static checks (as adopted by repo stage) + +Rules: + +1. No bypass by default. +2. Hook runtime should stay fast (target under ~60s on normal changes). +3. Integration tests are not required in pre-commit. + +## 4) CI Requirements (Required) + +Every PR must run: + +1. lint/format checks +2. `make test` +3. selected integration tests (or full `make test-integration` where feasible) + +Main branch protection should require CI green before merge. + +## 5) Test Strategy + +1. Prefer table-driven unit tests. +2. Keep business logic testable without Docker where possible. +3. Use integration tests for external systems and process boundaries only. +4. Keep package-level coverage trending to 90%+ over time. + +## 6) Versioning and Tooling + +1. Pin project tools to declared versions (do not auto-latest on normal commands). +2. Provide a doctor/check command to detect version drift. +3. Use explicit upgrade workflows for intentional version bumps. + +## 7) Documentation Requirements + +For each behavior/architecture change: + +1. Update relevant `docs/*.md` files in the same change stream. +2. Keep CLI contract in `docs/reference/01-cli.md` current. +3. Keep framework plan (`docs/roadmap/01-framework-plan.md`) aligned with decisions. + +## 8) Commit and PR Standards + +1. Use conventional commit prefixes (`feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`). +2. Keep commits scoped and reviewable. +3. PR description must include: +: what changed +: why it changed +: test evidence +: docs updated + +## 9) Standalone Repo Readiness Checklist + +A module/repo is ready to stand alone when: + +1. it has its own `README` and usage commands +2. it has independent tests passing in CI +3. it has pinned toolchain policy documented +4. it has release/versioning policy documented +5. it can be developed with minimal implicit dependency on sibling repos + +--- +FILE: docs/reference/01-cli.md +--- +# CLI Specification (Living) + +This file is the living CLI contract for developers and agents. + +Short command name: + +- `ship` + +Module location: + +- `cli/ship` (standalone Go module) +- binary entrypoint: `cli/ship/cmd/ship` +- companion MCP module: `mcp/ship` (for LLM-facing tool access) + +## Repository Placement + +The CLI is in the same repository as the framework and example app. + +- Repo model: monorepo with multiple Go modules. +- App/framework module: repository root. +- CLI module: `cli/ship`. +- Workspace: `go.work` includes both modules for local development. + +Why this shape: + +1. Single repo keeps framework, app example, and CLI evolution in sync. +2. Separate CLI module keeps dependency graph and release surface clean. +3. Developers can iterate across modules locally without publishing interim versions. + +Design constraints: + +1. Keep commands Rails-like and convention-first. +2. Keep v1 command set intentionally small. +3. Expand only after each command is stable and tested. + +## Minimal V1 Command Set + +Project lifecycle: + +- `ship new ` (planned) +- `ship doctor` (planned) + +Local runtime: + +- `ship dev` (web-only default) +- `ship dev --worker` +- `ship dev --all` + +Testing: + +- `ship test` (unit default) +- `ship test --integration` + +Database: + +- `ship db create` +- `ship db migrate` +- `ship db rollback` +- `ship db seed` + +Generation: + +- `ship generate ` (planned) +- `ship destroy ` (planned) + +## Versioning Rules + +1. CLI-managed tools (for example `templ`) must be pinned to project-declared versions. +2. `ship dev` and `ship test` must never auto-upgrade toolchain versions. +3. `ship doctor` reports version drift and prints explicit fix commands. +4. Only `ship upgrade` (future) may intentionally bump pinned versions. + +## Implementation Mapping (Current Repo) + +These commands are implemented as wrappers over existing workflows: + +- `ship dev` -> `make dev` +- `ship dev --worker` -> `make dev-worker` +- `ship dev --all` -> `make dev-full` +- `ship test` -> `make test` +- `ship test --integration` -> `make test-integration` +- `ship db create` -> `make up` +- `ship db migrate` -> `make migrate` +- `ship db rollback [amount]` -> `atlas migrate down ... [amount]` +- `ship db seed` -> `make seed` + +Local run examples from repository root: + +- `go run ./cli/ship/cmd/ship -- help` +- `go run ./cli/ship/cmd/ship -- dev` + +## Ownership Boundaries + +CLI owns: + +- developer command interface (`ship ...`); +- orchestration of dev/test/db workflows; +- version/tooling checks and future generators. + +App/framework owns: + +- actual runtime behavior in `cmd/*`, `app/goship/*`, `pkg/*`, and `config/*`. + +Rule: + +- keep business/runtime logic out of CLI package; CLI should call stable commands/APIs. + +## Deferred (Not In V1) + +- `ship console` +- `ship routes` +- `ship upgrade` +- advanced generator variants + +--- +FILE: docs/reference/02-mcp.md +--- +# MCP Server (ship-mcp) + +This document defines the minimal MCP server currently shipped in this repository. + +## Status + +Current status: + +1. Not actively used in day-to-day framework development right now. +2. Kept as a future extension point for LLM-facing tooling. +3. Near-term priority is high-quality human + LLM-friendly documentation under `docs/`. + +## Location + +- Module: `mcp/ship` +- Entrypoint: `mcp/ship/cmd/ship-mcp/main.go` +- Workspace wiring: `go.work` + +This is a standalone Go module in the same monorepo as: + +- app/framework module at repo root +- CLI module at `cli/ship` + +## Purpose + +Provide an LLM-facing interface for: + +1. `ship` command usage guidance. +2. focused access to docs under `docs/`. + +## Current Tool Set (V1) + +1. `ship_help` +- Returns usage/help text for core `ship` commands. +- Input: optional `topic` (`general`, `dev`, `test`, `db`). + +2. `docs_search` +- Searches markdown files under `docs/`. +- Input: `query` (required), `limit` (optional, default 20, max 50). + +3. `docs_get` +- Returns a single markdown document from `docs/`. +- Input: `path` (required), relative to `docs/`. + +## Runtime Notes + +- Transport: stdio with MCP JSON-RPC framing (`Content-Length` headers). +- Default docs root: `docs`. +- Override docs root with `SHIP_MCP_DOCS_ROOT`. + +Run from repo root: + +```bash +go run ./mcp/ship/cmd/ship-mcp +``` + +## Install In MCP Clients + +Build a reusable local binary first: + +```bash +go build -o ~/.local/bin/ship-mcp ./mcp/ship/cmd/ship-mcp +``` + +Use an absolute docs path: + +```bash +export SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs +``` + +Register in Codex: + +```bash +codex mcp add ship --env SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs -- ~/.local/bin/ship-mcp +codex mcp list +``` + +Register in Claude Code: + +```bash +claude mcp add --scope user -e SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs ship -- ~/.local/bin/ship-mcp +claude mcp list +``` + +Register in Gemini CLI: + +```bash +gemini mcp add --scope user -e SHIP_MCP_DOCS_ROOT=/Users/leoaudibert/Workspace/pagoda-based/goship/docs ship ~/.local/bin/ship-mcp +gemini mcp list +``` + +Notes: + +1. `--scope user` makes this MCP server available globally; use project/local scope for repo-only registration. +2. Restart your CLI session after adding a new MCP server. +3. If `~/.local/bin` is not on `PATH`, keep using the absolute binary path. + +## Release Binaries (MCP Internals) + +`ship-mcp` prebuilt binaries are published from this repository using GoReleaser and GitHub Actions. + +Internal release files: + +- `.goreleaser.ship-mcp.yml` +- `.github/workflows/release-ship-mcp.yml` + +Workflow trigger policy: + +1. Tag push only: `ship-mcp/v*` +2. Manual trigger allowed: `workflow_dispatch` +3. No release on regular branch pushes + +Tag example: + +```bash +git tag ship-mcp/v0.1.0 +git push origin ship-mcp/v0.1.0 +``` + +What gets published: + +1. `ship-mcp` binaries for `linux`, `darwin`, `windows` +2. Architectures: `amd64`, `arm64` +3. Checksums file: `ship-mcp_checksums.txt` + +## Safety Rules + +1. `docs_get` only reads inside `docs/` (path traversal blocked). +2. large doc payloads are truncated. +3. unknown methods/tools return structured JSON-RPC errors. + +## Near-Term Extensions + +1. Add a `ship_run` tool with allowlisted commands. +2. Add doc IDs that map to numbered filenames for stable retrieval. +3. Add cross-doc link graph output for faster agent navigation. + +--- +FILE: docs/roadmap/01-framework-plan.md +--- +# GoShip Framework Plan + +This document tracks the plan to evolve GoShip from a starter kit into a Rails-like Go framework with strong developer ergonomics. + +## Terminology Note + +Voice-to-text aliases used in discussion: + +- `shiri` => `Cherie` +- `jerry` => `Cherie` +- `sherry` => `Cherie` + +When planning or tracking work, treat all of the above as `Cherie`. + +## Vision + +Build a highly productive, convention-first Go framework where developers can: + +1. ship fast with sensible defaults; +2. install batteries as versioned modules; +3. choose deployment/runtime mode without rewriting app code. + +## Documentation Source Of Truth + +Implementation and architecture documentation lives in `docs/`. + +Primary files for ongoing refactor work: + +1. `docs/architecture/02-structure-and-boundaries.md` (canonical placement rules) +2. `docs/architecture/01-architecture.md` (runtime/system behavior) +3. `docs/guides/01-ai-agent-guide.md` (agent execution conventions) +4. `docs/reference/01-cli.md` (living `ship` CLI contract) + +Documentation execution priority: + +1. Docs-first is preferred over MCP/tooling-first in the near term. +2. `ship-mcp` remains a future extension point, not a primary workflow dependency today. +3. Build task-focused how-to guides that make common changes fast and repeatable. + +Primary framing: + +- GoShip aims to be a Ruby on Rails alternative in Go, with comparable batteries-included productivity and developer ergonomics. +- Product aspiration: be a deeply loved framework by developers by putting developer joy, speed, and clarity first. +- Rails inspiration applies to the entire framework experience (not only environment settings): app structure, conventions, generators, batteries, defaults, testing, and deployment workflows. + +### Rails-Inspired Framework Pillars + +1. Convention over configuration: +- opinionated defaults for layout, modules, and naming; +- explicit escape hatches when teams need control. +2. Generators-first workflow: +- create apps/resources/jobs/modules with minimal manual wiring. +3. Batteries-included, modular delivery: +- auth, billing, notifications, storage, admin, jobs as installable modules. +4. Stable abstractions over pluggable implementations: +- developers code to GoShip interfaces; adapters are swap-friendly. +5. Excellent local DX: +- fast feedback, minimal setup, stateless tests by default. +6. Production-ready path: +- clear migration from local single-process to distributed process topology. + +Rails inspiration for configuration and "what runs": + +1. Rails uses layered configuration: +- `config/application.rb` for global app config; +- `config/environments/*.rb` for per-environment overrides; +- `config/initializers/*.rb` for subsystem wiring; +- Gemfile/Bundler groups for dependency activation. +2. GoShip equivalent target: +- global framework/module manifest + adapter selection; +- per-environment overrides; +- startup wiring that follows enabled modules/adapters. + +### Execution Topology and File Approach + +To decide what runs in parallel (web/worker/scheduler) and with which adapters, use layered config files: + +1. `config/application.yaml`: +- global defaults; +- module enablement; +- adapter defaults (`db`, `cache`, `jobs`, `pubsub`, `storage`). +2. `config/environments/{local,dev,test,prod}.yaml`: +- per-environment overrides; +- profile selection (`server-db`, `single-node`, `distributed`). +3. `config/processes.yaml`: +- process topology matrix: + - `web` (bool) + - `worker` (bool) + - `scheduler` (bool) + - `co_located` (bool) +- examples: + - local default: web=true, worker=true, scheduler=true (single process with goroutines if backend supports it) + - prod distributed web: web=true, worker=false, scheduler=false + - prod worker: web=false, worker=true, scheduler=true +4. `config/initializers/*.go` (generated/wired by CLI): +- runtime boot wiring based on enabled modules/adapters/processes. + +CLI responsibilities: + +1. `goship new` writes initial config set with sane defaults. +2. `goship profile set ` updates environment/process presets. +3. `goship module add ` updates module manifest + initializer wiring. +4. `goship jobs backend set ` updates adapter config with capability checks. +5. `goship new` should auto-install templ tooling and keep it current in generated projects (including `go get -u github.com/a-h/templ` as part of bootstrap/update workflow). + +### CLI Surface (Rails-Inspired) + +Primary command groups to match Rails ergonomics while staying Go-native: + +1. Project lifecycle: +- `goship new ` +- `goship upgrade` +- `goship doctor` +2. Runtime/developer workflow: +- `goship dev` (web-only default) +- `goship dev --worker` +- `goship dev --all` +- `goship test` (unit default) +- `goship test --integration` +3. Code generation: +- `goship generate ` +- `goship destroy ` +4. Modules/adapters: +- `goship module add ` +- `goship module remove ` +- `goship adapter set ` +5. Data/schema: +- `goship db:migrate` +- `goship db:rollback` +- `goship db:seed` + +Rules for versioned tooling in generated apps: + +1. CLI installs tool versions pinned to the project declaration. +2. CLI does not auto-upgrade tools to latest on dev/test commands. +3. `goship doctor` reports drift (e.g., templ CLI older/newer than project version) and provides fix commands. +4. `goship upgrade` is the only command that intentionally bumps pinned tool/module versions. + +## Core Product Goals + +1. Rails-like productivity in Go. +2. LLM-first developer experience. +3. Convention over configuration. +4. Modular infrastructure adapters. +5. Strong defaults with optional escape hatches. + +## Current Repository Shape (Post-Refactor) + +1. `app/goship/` contains app-specific web handlers and templ views. +2. `pkg/` currently remains the framework/infrastructure layer. +3. `cmd/` contains process entrypoints. + +Note: `pkg/repos` and `pkg/services` are intentionally still centralized for now and will be split into app-specific vs reusable framework modules in a dedicated follow-up pass. + +## Architecture Style (Pragmatic) + +GoShip will use a pragmatic blend: + +1. Rails-style developer experience at the framework surface. +2. Selective clean-architecture boundaries only where they add real value. + +What this means: + +1. Do not implement clean architecture verbatim across every layer. +2. Use interfaces at infrastructure seams so backend technology is swappable. +3. Keep core app/domain logic straightforward and low-ceremony. + +Primary infrastructure seams to abstract: + +1. `Store` (database) +2. `Cache` +3. `Jobs` +4. `PubSub` +5. `BlobStorage` +6. `Mailer` + +Non-goal: + +- Avoid abstracting everything "just in case"; abstractions must improve portability or testability. + +## Confirmed Decisions + +1. Keep Ent as the ORM for now. +2. Use a monorepo with multiple Go modules plus `go.work` for maintainers. +3. Ship installable/versioned modules (auth, billing, notifications, storage, admin). +4. Keep one blessed default stack, but support both single-node and distributed runtime modes via adapters. +5. Near-term default is database-server-first (Postgres first), not SQLite-centric. +6. Redis is optional capability, not a hard requirement. + +## Upstream/Downstream Relationship + +GoShip is the framework upstream. +Cherie is a downstream product built on top of GoShip. + +Framework work must include a sync path so Cherie stays current without fragile manual cherry-picking. + +## Candidate Capabilities To Pull From Cherie + +Based on Cherie docs and current implementation, these are strong candidates to upstream into GoShip modules: + +1. Realtime baseline that is fully wired (SSE endpoint + unread counts + notification center patterns). +2. Mature notification permissions model (type + platform + grant/revoke lifecycle). +3. Background job patterns for daily/periodic workflows (retention, maintenance, notification orchestration). +4. Referral system primitives (link generation, attribution, reward application). +5. Gamification primitives (points/progression hooks) as optional module. +6. Multi-app branding strategy from one codebase (app profile/brand config). +7. Security hardening patterns: +- route-level authorization checks for resource interaction; +- friend/relationship ownership checks where relevant; +- explicit forbidden/not-found behavior for unauthorized access. +8. Production operations runbooks: +- deploy profile separation; +- cache invalidation/ops guidance; +- migration caveats and guardrails. + +## Pagoda Upstream Intake Plan + +Long-term policy: + +1. Treat Pagoda as an upstream source of framework/runtime improvements. +2. Regularly evaluate and selectively port changes into GoShip. +3. Do not adopt Pagoda UI/component layer choices that conflict with GoShip direction. + +Current known upstream shifts in Pagoda (to evaluate and/or port): + +1. Default move from Postgres+Redis to SQLite-centric operation (reference only, not the current GoShip default direction). +2. Migration from Asynq to Backlite for DB-backed task queues. +3. In-process task runner startup in web process, with graceful task shutdown in container. +4. Use of in-memory cache as default for simpler local development. +5. Admin/task runtime integration improvements. + +Non-goals for direct adoption: + +1. Go-based HTML component stack from Pagoda (`gomponents`) as a hard dependency. +2. Any upstream UI architecture changes that reduce GoShip's Templ+HTMX ergonomics. +3. Forcing GoShip into SQLite-centric defaults at this stage. + +## Documentation Quality Initiative + +Primary goal: + +1. Deliver documentation quality at least equal to Pagoda's onboarding clarity, and better on practical implementation guides. + +Near-term deliverable: + +1. Build out `docs/guides/03-how-to-playbook.md` into concrete how-to guides for common engineering tasks. + +Initial guide set: + +1. Add endpoint +2. Add page/view +3. Add model + migration +4. Add service/repo +5. Add background job +6. Add adapter +7. Add tests + +## Pagoda Intake TODOs + +- [ ] Create a recurring upstream review cadence (weekly or per-tag) for Pagoda. +- [ ] Add a "Pagoda intake log" mapping upstream commit/tag -> GoShip decision (`adopt`, `adapt`, `skip`). +- [ ] Evaluate Backlite-style DB-backed jobs as a GoShip jobs adapter candidate. +- [ ] Port container lifecycle hardening patterns where applicable (startup/shutdown ordering and timeouts). +- [ ] Port testability improvements that reduce Docker dependence. +- [ ] Keep UI/component layer decisions independent from runtime/service layer intake. +- [ ] Prefer LLM-assisted feature re-implementation over direct commit cherry-picks due codebase divergence. + +## Cherie Sync Policy + +1. Every GoShip framework milestone must include a Cherie compatibility check. +2. Breaking changes require: +- migration notes; +- codemods or scripted upgrade steps where possible; +- a compatibility window policy. +3. Maintain a living "GoShip -> Cherie adoption board" with statuses: +- `not started` +- `in progress` +- `adopted` +- `blocked` +4. Do not merge major framework refactors without validating Cherie boot, auth flow, and realtime flow. + +## Runtime Modes + +### 1) Server-DB Mode (Primary Near-Term Default) + +- DB: external DB server (Postgres first; MySQL later through adapter boundary) +- Cache: in-memory by default +- Jobs: pluggable (`inproc` for simplicity, durable backend for reliability) +- Redis: optional, not required + +### 2) Single-Node Mode (Future-Friendly Profile) + +- DB: SQLite +- Cache: in-memory +- PubSub: in-process +- Jobs: in-process scheduler/worker + +### 3) Distributed Mode + +- DB: Postgres +- Cache: adapter-driven (Redis optional) +- PubSub: adapter-driven +- Jobs: adapter-driven (DB-backed queue or external queue service) + +## Worker Queue Abstraction Strategy + +Goal: + +- one stable app-facing jobs API with multiple backend implementations. + +Design principles: + +1. Define a minimal stable core contract in `goship/jobs`: +- `Register(name, handler)` +- `Enqueue(name, payload, opts...)` +- `StartWorker(ctx)` +- `StopWorker(ctx)` +- `StartScheduler(ctx)` (if supported) +2. Use capability declarations per backend (delayed jobs, retries, cron, priority, dead-letter, UI). +3. Validate feature usage against backend capabilities at startup. +4. Keep backend-specific settings in adapter config, not spread in app code. +5. Keep handlers/payload contracts backend-agnostic. +6. Introduce capability contracts so unsupported backend features fail fast at startup. + +Planned adapters: + +1. `jobs/inproc` (best local DX) +2. `jobs/dbqueue` (DB-backed durable queue, no Redis required) +3. `jobs/asynq` (optional Redis-backed adapter) +4. future cloud adapters (Pub/Sub / Cloud Tasks bridges) + +## Backend-Agnostic Framework Rule + +Like Rails/Django, GoShip should not force one database/cache choice. + +Policy: + +1. Framework APIs remain backend-agnostic. +2. Backend selection happens in config + runtime wiring. +3. Application/business code must not depend directly on concrete infra clients. + +## Routing Organization (Rails-Inspired, Pragmatic) + +Target: + +1. Keep one orchestration router entrypoint. +2. Register routes by domain slices (auth, public, docs, billing, notifications, etc.). +3. Keep domain registration functions small and convention-driven. +4. Avoid over-engineered plugin systems in early stages. + +Current direction: + +1. `BuildRouter` handles shared middleware and runtime feature gating. +2. Domain registration is split into focused files: +- public +- docs +- auth +- external +- realtime +3. App composition happens via a single route composition function, preparing for multi-app mounting later. + +## Required Core Interfaces + +Create stable contracts in `core` so app code is backend-agnostic: + +1. `Store` (database/repository boundary) +2. `Cache` +3. `PubSub` +4. `Jobs` +5. `SessionStore` +6. `BlobStore` +7. `Auth` +8. `Billing` +9. `Notifications` + +## Rails-Like Capabilities to Implement First + +1. `goship new ` CLI with a 2-minute happy path. +2. Generators: +- `goship g model` +- `goship g scaffold` +- `goship g migration` +- `goship g job` +- `goship g mailer` +- `goship g module install ` +3. ActiveStorage-like file attachments: +- attach files to entities; +- support local + S3 backends; +- simple URL + variant APIs. +4. Admin scaffolding from Ent schema. +5. Background jobs with retries/scheduling. + +## Frontend Strategy (HTMX-First, Svelte as Islands) + +Svelte should remain optional and isolated. Current pattern in Cherie/GoShip bundles all Svelte entrypoints into one global `svelte_bundle.js`, which increases payload and causes coupling. + +Target approach: + +1. Keep HTMX + Templ as default. +2. Load Svelte only for pages/components that need it. +3. Replace single global Svelte bundle with per-island chunks (no single global Svelte build artifact loaded site-wide). +4. Use auto-discovery to find islands and generate a manifest + runtime registry automatically. +5. Use dynamic imports so `renderSvelteComponent(name, ...)` loads code on demand. +6. Avoid globally injecting `svelte_bundle.js` for all pages. + +Important DX constraint: + +- No manual island wiring by default. +- Island wiring should be generated by CLI/scaffolding. + +### Auto-Discovery + Auto-Wire Model + +Desired developer flow: + +1. Create a Svelte component either: +- next to its usage (colocated), or +- in a central reusable island library. +2. Reference it in Templ with a helper/component tag and props. +3. Build step auto-discovers island files and generates: +- island manifest (`component -> js/css asset`); +- runtime registry for lazy mounting; +- optional typed helper stubs. +4. Templ helper injects only needed island script(s) for that page/partial. + +No manual edits should be required for: + +- central JS registry files; +- script tag wiring in templates; +- per-component import maps. + +## Svelte/Islands TODOs + +- [ ] Replace single `svelte_bundle.js` build with multiple outputs (code-split entries). +- [ ] Add an island manifest mapping `componentName -> asset path`. +- [ ] Add template helper to include only required island scripts per page. +- [ ] Support HTMX swap lifecycle: mount/unmount Svelte instances safely after partial swaps. +- [ ] Benchmark before/after page payload and interaction latency. +- [ ] Document "when to use Svelte vs Alpine vs vanilla vs pure HTMX" as framework guidance. +- [ ] Add `goship g island ` to generate component + entrypoint + registration with zero manual edits. +- [ ] Support colocated islands and central-library islands with the same discovery pipeline. +- [ ] Add a watch mode that re-generates island manifest/registry automatically during development. + +## Frontend Alternatives (No-Compile Paths) + +Question: can we load raw `.svelte` components directly in browser without build? + +Answer: + +- Not as a production default. Svelte is compile-based. + +Viable alternatives for no-build interactivity: + +1. HTMX + Alpine (preferred default for small/medium interactions). +2. Vanilla Web Components for reusable widgets. +3. Lightweight runtime libraries (e.g. Petite-Vue) where appropriate. + +Decision: + +- Keep Svelte optional for advanced islands. +- Keep HTMX-first and no-build-friendly by default. +- Ensure CLI removes manual compile-pipeline pain where Svelte is used. + +## Unified Styling Policy + +Goal: + +- one unified styling system across GoShip modules and generated apps. + +Preferred direction: + +1. Use a single design system source of truth (tokens, component variants, spacing, colors, typography). +2. If Svelte is used, keep style parity with the same design tokens/components (e.g. shadcn + shadcn-svelte style equivalence). +3. Avoid fragmented ad-hoc component styling across stacks. + +LLM/agent styling guardrails: + +1. LLMs may change HTML/Templ/Svelte structure and behavior logic. +2. LLMs should not freely rewrite Tailwind styling. +3. Styling changes must be centralized in designated style system files with explicit documentation comments. +4. Generated/component docs should clearly mark style contract boundaries as "do not mutate directly" unless requested. +5. Framework prompts/checklists should enforce: "change behavior first, preserve style tokens/classes unless style task is explicitly requested." + +## Testing Strategy (Developer Ergonomics First) + +Test workflow should be fast and local-first without requiring Docker for most feedback loops. + +Principles: + +1. Maximize unit tests and table-driven tests for business logic. +2. Push side effects behind interfaces to allow in-memory fakes. +3. Keep integration tests focused and limited (happy-path + critical failure cases). +4. Keep end-to-end tests minimal and scenario-driven. +5. Avoid making Docker a prerequisite for routine test runs. + +Target pyramid: + +1. Unit tests (majority): pure Go, table tests, no network, no containers. +2. Integration tests (few): DB/repo boundaries and adapter contracts. +3. E2E tests (very few): key user journeys only. + +### Pre-Commit Test Policy + +1. Every commit must pass `lefthook` pre-commit tests. +2. Pre-commit runs a fast, stateless Go unit suite only. +3. Docker/integration suites run separately (manual or CI), not as a local pre-commit default. +4. As packages are refactored, they should be moved into the pre-commit suite. + +### Agent-Driven Documentation and Downstream Sync Policy + +For every implementation change and commit: + +1. Update developer + LLM-oriented docs in the same change stream. +2. Keep docs split into focused markdown files by area/topic rather than one giant file. +3. Reflect behavior changes in framework docs and LLM reference docs. +4. Add/update downstream impact notes for Cherie when GoShip changes affect integration. +5. Treat documentation sync and Cherie sync as required agent checks. + +## Commit Standard + +Use Conventional Commits for all framework work: + +`type(scope): imperative summary` + +Allowed types: + +- `feat` +- `fix` +- `refactor` +- `test` +- `docs` +- `chore` +- `ci` + +Examples: + +- `fix(services): make container shutdown nil-safe` +- `test(services): add nil-safe shutdown coverage` +- `docs(plan): define first rework execution workflow` + +## Module Plan + +Proposed module boundaries: + +1. `packages/core` +2. `packages/auth` +3. `packages/billing` +4. `packages/notifications` +5. `packages/storage` +6. `packages/admin` +7. `cli/ship` + +## Priority Roadmap + +### Phase 0: Stabilize Current Base + +1. Fix container initialization mismatch and shutdown safety. +2. Resolve realtime/notification route wiring drift. +3. Clean and align current docs with actual runtime behavior. +4. Refresh stale e2e coverage for critical flows. + +## First Rework Execution Plan + +This section is the active implementation tracker for the first rework pass. +Rule: execute exactly one task at a time, validate with tests, then move to the next task. + +### Quality Gates (for every task) + +1. Add or update tests with the change (prefer table-driven tests). +2. Run targeted tests for touched package(s). +3. Keep tests mostly stateless (no Docker for default test path). +4. No task is marked complete without test evidence. + +### Coverage Targets + +1. Global target: 90%+ over time (not required in one PR). +2. Reworked packages should trend toward 90%+ before moving on. +3. Complex pure-logic packages should aim for near-100% branch coverage with table tests. + +### Active Task Queue (First Rework) + +1. `R0.1` Container shutdown safety + reliable container unit test baseline. +Status: `completed` +Done when: +- `Container.Shutdown()` is nil-safe for optional services. +- container unit tests compile and pass without external services. +Test evidence: +- `go test ./pkg/services -run 'Test(NewContainer|ContainerShutdownNilSafe)$'` + +2. `R0.2` Container initialization policy by runtime mode (`single-node` vs `distributed`), with explicit config contract. +Status: `completed` +Done when: +- runtime/process/adapters config scaffold exists; +- runtime plan resolver exists with table tests; +- no startup behavior change yet (scaffold only). +Test evidence: +- `go test ./config ./pkg/runtimeplan` + +3. `R0.3` Router consistency pass (realtime + notifications wired consistently with initialized dependencies). +Status: `completed` +Done when: +- router enables cache middleware only when cache dependency is available; +- realtime routes are wired only when notifier+pubsub dependencies are available; +- runtime plan is resolved at router build with safe fallback on invalid plan configuration. +Test evidence: +- `go test ./pkg/runtimeplan` + +4. `R0.4` Testing harness improvements so default `make test` is Docker-free and fast. +Status: `completed` +Done when: +- default `make test` executes a Docker-free unit package set; +- integration/infra-heavy tests run via separate command; +- cache-dependent unit tests do not fail when cache service is disabled in runtime profile. +Test evidence: +- `bash scripts/test-unit.sh` + +5. `R0.5` Establish package-level coverage baselines and close highest-value test gaps. +Status: `in_progress` + +### Phase 1: Core Abstractions + +1. Define `core` interfaces for DB/cache/pubsub/jobs/storage. +2. Implement adapters: +- SQLite + Postgres +- memory-cache + Redis-cache +- inproc-pubsub + Redis-pubsub +- inproc-jobs + Asynq +3. Add runtime mode config (`single-node`, `distributed`). + +### Phase 2: Monorepo and Module Packaging + +1. Restructure into multi-module layout. +2. Add `go.work` for local development across modules. +3. Establish semver tagging and module release process. +4. Define how Cherie consumes modules during local dev (`go.work`) vs released versions (tags). + +### Phase 3: CLI and Generators + +1. Build `goship` CLI. +2. Implement app bootstrap and generator commands. +3. Add idempotent install/wire commands for optional modules. + +### Phase 4: Batteries and DX + +1. Deliver auth, storage, notifications, billing, admin modules. +2. Implement ActiveStorage-like attachment primitives. +3. Improve diagnostics, error pages, and test templates. + +### Phase 5: LLM-First Tooling + +1. Add `llm.txt` as machine-readable framework reference. +2. Add an MCP server exposing commands, module contracts, and examples. +3. Generate concise human docs from the same source of truth. + +## TODO Checklist + +## Immediate + +- [ ] Decide and document exact package naming convention (`goship/*`). +- [ ] Choose CLI implementation approach (`cobra`, `urfave/cli`, or stdlib). +- [ ] Draft `core` interface contracts in a design doc. +- [ ] Define runtime config schema for adapter selection. +- [ ] Specify module compatibility/version policy. +- [ ] Create a developer-facing README + LLM-facing README/`llm.txt` split with one source of truth. +- [ ] Define MCP server scope for GoShip (commands, module APIs, recipes, migration help). +- [ ] Create a `CHERIE_SYNC.md` runbook (upgrade process + rollback + validation checklist). +- [ ] Create a baseline compatibility test suite for Cherie critical paths. +- [ ] Define testing standards doc: what must be unit-testable and where table tests are required. +- [ ] Add doc-sync guardrails in pre-commit/CI for framework-impacting changes. +- [ ] Add Cherie-sync guardrails in pre-commit/CI (or mandatory checklist gate). + +## Near-Term + +- [ ] Build first adapter pair: `sqlite` + `postgres`. +- [ ] Build first cache pair: `memory` + `redis`. +- [ ] Build first pubsub pair: `inproc` + `redis`. +- [ ] Build first jobs pair: `inproc` + `asynq`. +- [ ] Prototype attachment API with local and S3 storage. +- [ ] Refactor high-logic route/service code into testable units with interface boundaries. +- [ ] Add in-memory test doubles for cache/pubsub/jobs/storage adapters. +- [ ] Ensure default `make test` runs without Docker. + +## Mid-Term + +- [ ] Release `goship new` CLI command. +- [ ] Release `goship g model` and `goship g migration`. +- [ ] Release `auth` and `storage` modules. +- [ ] Release `admin` scaffolding MVP. +- [ ] Add golden-path example apps for both runtime modes. +- [ ] Move Cherie onto released GoShip modules incrementally (module by module). +- [ ] Upstream selected Cherie capabilities into optional GoShip modules (notifications/referrals/gamification/security helpers). +- [ ] Keep Docker-based integration suite as optional/CI-focused (`make test-integration`), not default local path. + +## Open Questions + +1. How strict should conventions be before allowing customization hooks? +2. Which features are mandatory in v1 versus module-only? +3. What is the minimum stable API surface for `core` v1.0.0? +4. How should we guarantee cross-module compatibility at release time? + +## Definition of Success (v1) + +1. A developer can run `goship new myapp` and ship a working app quickly. +2. The same app code can run in single-node or distributed mode by config. +3. Optional batteries are added via CLI without copy-paste. +4. LLMs can reliably reason over framework structure using `llm.txt` + MCP. +5. Cherie can upgrade to current GoShip with a documented, repeatable process. + diff --git a/Makefile b/Makefile index 2c98ec27..9ddaef8a 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,10 @@ ensure-compose: hooks: ## Install git hooks via lefthook lefthook install +.PHONY: llm-txt +llm-txt: ## Generate root LLM.txt from README and docs markdown files + bash scripts/generate-llm-txt.sh + # Core workflow ------------------------------------------------------------------------------ .PHONY: dev diff --git a/docs/00-index.md b/docs/00-index.md index e8264886..5230263e 100644 --- a/docs/00-index.md +++ b/docs/00-index.md @@ -29,6 +29,7 @@ It is not intended as user-facing product documentation. 1. `G01` - `guides/01-ai-agent-guide.md`: practical guide for AI agents working in this repo. 2. `G02` - `guides/02-development-workflows.md`: day-to-day run/build/test/migration workflows. +3. `G03` - `guides/03-how-to-playbook.md`: prioritized how-to guide backlog and writing template. ### Reference diff --git a/docs/guides/03-how-to-playbook.md b/docs/guides/03-how-to-playbook.md new file mode 100644 index 00000000..da24db1e --- /dev/null +++ b/docs/guides/03-how-to-playbook.md @@ -0,0 +1,45 @@ +# How-To Playbook (Docs-First) + +This file tracks practical how-to guides we want to provide for GoShip. + +## Objective + +Create docs quality equal to or better than Pagoda's onboarding experience, with concrete implementation guides for common tasks. + +## Priority Guides + +1. Add a new endpoint (route + handler + templ view + tests). +2. Add a new page with server-rendered UI. +3. Add a new Ent model and migration flow. +4. Add a new service/repository with boundaries (app vs framework placement). +5. Add a background job and wire worker behavior. +6. Add a module adapter (db/cache/jobs/pubsub/storage) with interface wiring. +7. Add realtime event flow (publish + subscribe + UI update). +8. Add authentication-protected endpoint and authorization check. +9. Add table-driven unit tests for routes/repos/services. +10. Add integration test (happy path only, Docker-minimal). + +## Guide Template (Use For Every How-To) + +1. Goal: one-sentence desired outcome. +2. Preconditions: files, commands, env vars needed. +3. Steps: exact edits/commands in order. +4. Validation: tests/commands and expected output. +5. Common failures: top 3 mistakes and fixes. +6. References: links to canonical docs and source files. + +## Writing Rules + +1. Prefer copy-pastable commands and exact file paths. +2. Use current numbered docs paths in all references. +3. Keep examples aligned with `ship` commands where available. +4. Every guide must include a test/verification section. +5. Keep sections short and task-oriented; avoid long conceptual digressions. + +## Execution TODO + +- [ ] Draft guide: Add a new endpoint. +- [ ] Draft guide: Add a new Ent model and migration. +- [ ] Draft guide: Add a background job. +- [ ] Draft guide: Add tests (table-driven + integration). +- [ ] Draft guide: Add a module adapter. diff --git a/docs/reference/02-mcp.md b/docs/reference/02-mcp.md index 80fced75..b839ce28 100644 --- a/docs/reference/02-mcp.md +++ b/docs/reference/02-mcp.md @@ -2,6 +2,14 @@ This document defines the minimal MCP server currently shipped in this repository. +## Status + +Current status: + +1. Not actively used in day-to-day framework development right now. +2. Kept as a future extension point for LLM-facing tooling. +3. Near-term priority is high-quality human + LLM-friendly documentation under `docs/`. + ## Location - Module: `mcp/ship` diff --git a/docs/roadmap/01-framework-plan.md b/docs/roadmap/01-framework-plan.md index 03c36209..16e35848 100644 --- a/docs/roadmap/01-framework-plan.md +++ b/docs/roadmap/01-framework-plan.md @@ -31,6 +31,12 @@ Primary files for ongoing refactor work: 3. `docs/guides/01-ai-agent-guide.md` (agent execution conventions) 4. `docs/reference/01-cli.md` (living `ship` CLI contract) +Documentation execution priority: + +1. Docs-first is preferred over MCP/tooling-first in the near term. +2. `ship-mcp` remains a future extension point, not a primary workflow dependency today. +3. Build task-focused how-to guides that make common changes fast and repeatable. + Primary framing: - GoShip aims to be a Ruby on Rails alternative in Go, with comparable batteries-included productivity and developer ergonomics. @@ -229,6 +235,26 @@ Non-goals for direct adoption: 2. Any upstream UI architecture changes that reduce GoShip's Templ+HTMX ergonomics. 3. Forcing GoShip into SQLite-centric defaults at this stage. +## Documentation Quality Initiative + +Primary goal: + +1. Deliver documentation quality at least equal to Pagoda's onboarding clarity, and better on practical implementation guides. + +Near-term deliverable: + +1. Build out `docs/guides/03-how-to-playbook.md` into concrete how-to guides for common engineering tasks. + +Initial guide set: + +1. Add endpoint +2. Add page/view +3. Add model + migration +4. Add service/repo +5. Add background job +6. Add adapter +7. Add tests + ## Pagoda Intake TODOs - [ ] Create a recurring upstream review cadence (weekly or per-tag) for Pagoda. diff --git a/lefthook.yml b/lefthook.yml index 2ff541db..002eeca3 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,6 +1,7 @@ pre-commit: parallel: false commands: + generate-llm-txt: + run: bash scripts/precommit-generate-llm-txt.sh unit-tests: run: bash scripts/precommit-tests.sh - diff --git a/scripts/generate-llm-txt.sh b/scripts/generate-llm-txt.sh new file mode 100755 index 00000000..ce1fcd6b --- /dev/null +++ b/scripts/generate-llm-txt.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +out_file="${repo_root}/LLM.txt" +tmp_file="${out_file}.tmp" + +# Build a deterministic ordered list of documentation sources. +declare -a files=() + +if [[ -f "${repo_root}/README.md" ]]; then + files+=("README.md") +fi + +if [[ -f "${repo_root}/docs/00-index.md" ]]; then + files+=("docs/00-index.md") +fi + +while IFS= read -r f; do + # 00-index is already included first when present. + if [[ "$f" == "docs/00-index.md" ]]; then + continue + fi + files+=("$f") +done < <(cd "${repo_root}" && find docs -type f -name '*.md' | sort) + +{ + echo "# LLM.txt" + echo + echo "Generated file. Do not edit manually." + echo "Source of truth is the markdown docs in this repository." + echo + + for rel in "${files[@]}"; do + abs="${repo_root}/${rel}" + if [[ ! -f "$abs" ]]; then + continue + fi + + echo "---" + echo "FILE: ${rel}" + echo "---" + cat "$abs" + echo + done +} > "${tmp_file}" + +if [[ -f "${out_file}" ]] && cmp -s "${tmp_file}" "${out_file}"; then + rm -f "${tmp_file}" + exit 0 +fi + +mv "${tmp_file}" "${out_file}" diff --git a/scripts/precommit-generate-llm-txt.sh b/scripts/precommit-generate-llm-txt.sh new file mode 100755 index 00000000..f2e51d34 --- /dev/null +++ b/scripts/precommit-generate-llm-txt.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +bash scripts/generate-llm-txt.sh + +git add LLM.txt From 6e0182849d5c2aaa9f73c618d5d8f38b5460cfc1 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 20:20:01 -0800 Subject: [PATCH 014/291] feat(config): enforce layered runtime config and docker smoke integration test --- Dockerfile | 13 +- LLM.txt | 9 +- config/{config.yaml => application.yaml} | 0 config/config.go | 131 +++++++++++++++--- config/config_test.go | 24 ++++ config/environments/dev.yaml | 4 + config/environments/local.yaml | 4 + config/environments/prod.yaml | 4 + config/environments/test.yaml | 4 + config/processes.yaml | 18 +++ docs/00-index.md | 4 +- .../architecture/03-project-scope-analysis.md | 2 +- docs/architecture/06-known-gaps-and-risks.md | 3 +- pkg/services/docker_image_smoke_test.go | 109 +++++++++++++++ 14 files changed, 292 insertions(+), 37 deletions(-) rename config/{config.yaml => application.yaml} (100%) create mode 100644 config/environments/dev.yaml create mode 100644 config/environments/local.yaml create mode 100644 config/environments/prod.yaml create mode 100644 config/environments/test.yaml create mode 100644 config/processes.yaml create mode 100644 pkg/services/docker_image_smoke_test.go diff --git a/Dockerfile b/Dockerfile index 3a10e797..e4e3239e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build the Go application -FROM golang:1.24.3-bullseye AS builder +FROM golang:1.25.6 AS builder # Set the working directory inside the container WORKDIR /app @@ -21,12 +21,11 @@ COPY . . ENV CGO_ENABLED=1 ENV GOOS=linux -ENV GOARCH=amd64 # Build the application -RUN GOARCH=amd64 go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-web ./cmd/web/main.go -RUN GOARCH=amd64 go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-worker ./cmd/worker/main.go -RUN GOARCH=amd64 go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-seed ./cmd/seed/main.go +RUN go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-web ./cmd/web/main.go +RUN go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-worker ./cmd/worker/main.go +RUN go build -ldflags="-s -w" -gcflags=all=-l -o /app/goship-seed ./cmd/seed/main.go # Install asynq tools RUN go install github.com/hibiken/asynq/tools/asynq@latest @@ -59,7 +58,9 @@ EXPOSE 8080 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -COPY config/config.yaml . +COPY config/application.yaml ./config/application.yaml +COPY config/processes.yaml ./config/processes.yaml +COPY config/environments/ ./config/environments/ COPY service-worker.js /service-worker.js COPY static /static diff --git a/LLM.txt b/LLM.txt index 702dbdf9..6b2f4875 100644 --- a/LLM.txt +++ b/LLM.txt @@ -629,7 +629,9 @@ It is not intended as user-facing product documentation. - `pkg/repos/**/*.go` - `ent/schema/*.go` - `config/config.go` -- `config/config.yaml` +- `config/application.yaml` +- `config/environments/*.yaml` +- `config/processes.yaml` - `Makefile` - `build.mjs` - `package.json` @@ -933,7 +935,7 @@ Build pipeline: Config loading: -- `config/config.go` + `config/config.yaml` +- `config/config.go` + layered YAML config (`application`, `environments/*`, `processes`) - Production can override via env vars (Viper env binding) Storage modes: @@ -1217,7 +1219,7 @@ Impact: ## 5) Dev Runtime Drift Between Config and Docker Compose (Medium) -- Default config uses embedded SQLite (`config/config.yaml`). +- Default base config currently keeps embedded SQLite defaults in `config/application.yaml`. - Docker Compose currently starts Redis and Mailpit only; DB service is commented out. - Make targets include Postgres-dependent commands. @@ -1249,7 +1251,6 @@ Impact: 4. Refresh e2e tests to match current GoShip flows. 5. Align local stack docs with actual DB mode and compose services. - --- FILE: docs/guides/01-ai-agent-guide.md --- diff --git a/config/config.yaml b/config/application.yaml similarity index 100% rename from config/config.yaml rename to config/application.yaml diff --git a/config/config.go b/config/config.go index d2e02ec8..37c6bd98 100644 --- a/config/config.go +++ b/config/config.go @@ -2,8 +2,10 @@ package config import ( "encoding/base64" + "errors" "fmt" "os" + "path/filepath" "strings" "time" @@ -232,42 +234,41 @@ type ( // GetConfig loads and returns configuration func GetConfig() (Config, error) { var c Config + roots := configSearchRoots() + v := viper.New() + v.SetConfigType("yaml") - // Common config loading - viper.SetConfigName("config") - viper.SetConfigType("yaml") - viper.AddConfigPath(".") - viper.AddConfigPath("config") - viper.AddConfigPath("../config") - viper.AddConfigPath("../../config") - viper.AddConfigPath("../../../config") - - // Load the config file - if err := viper.ReadInConfig(); err != nil { + // Base config is required. + if err := mergeNamedConfig(v, roots, "application.yaml", true); err != nil { return c, err } - // Unmarshal the config - if err := viper.Unmarshal(&c); err != nil { + // Unmarshal once to detect default environment before environment-specific overrides. + if err := v.Unmarshal(&c); err != nil { return c, err } + env := resolveEnvironment(c.App.Environment) - // Check the environment variable PAGODA_APP_ENVIRONMENT - env := os.Getenv("PAGODA_APP_ENVIRONMENT") - if env == string(EnvProduction) || c.App.Environment == EnvProduction { + // Layer per-environment overrides. + if err := mergeNamedConfig(v, roots, filepath.Join("environments", string(env)+".yaml"), true); err != nil { + return c, err + } + + // Production supports env var overrides with PAGODA_ prefix. + if env == EnvProduction { // Load env variables for production - viper.SetEnvPrefix("pagoda") - viper.AutomaticEnv() - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.SetEnvPrefix("pagoda") + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) } - // Load the config file - if err := viper.ReadInConfig(); err != nil { + if err := v.Unmarshal(&c); err != nil { return c, err } + c.App.Environment = env + applyRuntimeDefaults(&c) - // Unmarshal the config - if err := viper.Unmarshal(&c); err != nil { + if err := applyProcessesProfile(&c, roots); err != nil { return c, err } @@ -281,3 +282,87 @@ func GetConfig() (Config, error) { return c, nil } + +func configSearchRoots() []string { + return []string{ + ".", + "config", + "../config", + "../../config", + "../../../config", + } +} + +func resolveEnvironment(configured environment) environment { + if env := strings.TrimSpace(os.Getenv("PAGODA_APP_ENVIRONMENT")); env != "" { + return environment(env) + } + if configured != "" { + return configured + } + return EnvLocal +} + +func applyRuntimeDefaults(c *Config) { + if c.Runtime.Profile == "" { + c.Runtime.Profile = RuntimeProfileServerDB + } +} + +func mergeNamedConfig(dst *viper.Viper, roots []string, relPath string, required bool) error { + for _, root := range roots { + absPath := filepath.Clean(filepath.Join(root, relPath)) + info, err := os.Stat(absPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return fmt.Errorf("stat config file %q: %w", absPath, err) + } + if info.IsDir() { + continue + } + + src := viper.New() + src.SetConfigFile(absPath) + src.SetConfigType("yaml") + if err := src.ReadInConfig(); err != nil { + return fmt.Errorf("read config file %q: %w", absPath, err) + } + if err := dst.MergeConfigMap(src.AllSettings()); err != nil { + return fmt.Errorf("merge config file %q: %w", absPath, err) + } + return nil + } + + if required { + return fmt.Errorf("required config file %q not found in search paths", relPath) + } + return nil +} + +type processProfilesFile struct { + Profiles map[runtimeprofile]ProcessesConfig `mapstructure:"profiles"` +} + +func applyProcessesProfile(c *Config, roots []string) error { + pv := viper.New() + pv.SetConfigType("yaml") + if err := mergeNamedConfig(pv, roots, "processes.yaml", true); err != nil { + return err + } + + var profiles processProfilesFile + if err := pv.Unmarshal(&profiles); err != nil { + return fmt.Errorf("unmarshal processes profiles: %w", err) + } + if len(profiles.Profiles) == 0 { + return fmt.Errorf("processes.yaml missing profiles") + } + + if p, ok := profiles.Profiles[c.Runtime.Profile]; ok { + c.Processes = p + return nil + } + return fmt.Errorf("runtime profile %q not found in processes.yaml", c.Runtime.Profile) +} diff --git a/config/config_test.go b/config/config_test.go index 545062ee..2ae2e627 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -8,6 +8,8 @@ import ( ) func TestGetConfig(t *testing.T) { + t.Setenv("PAGODA_APP_ENVIRONMENT", "") + _, err := GetConfig() require.NoError(t, err) @@ -19,3 +21,25 @@ func TestGetConfig(t *testing.T) { assert.Equal(t, "postgres", cfg.Adapters.DB) assert.Equal(t, "inproc", cfg.Adapters.Jobs) } + +func TestGetConfig_EnvironmentOverrides(t *testing.T) { + t.Setenv("PAGODA_APP_ENVIRONMENT", string(EnvProduction)) + + cfg, err := GetConfig() + require.NoError(t, err) + assert.Equal(t, EnvProduction, cfg.App.Environment) + assert.Equal(t, RuntimeProfileDistributed, cfg.Runtime.Profile) + assert.True(t, cfg.Processes.Web) + assert.False(t, cfg.Processes.Worker) +} + +func TestGetConfig_UsesProcessProfile(t *testing.T) { + t.Setenv("PAGODA_APP_ENVIRONMENT", string(EnvLocal)) + + cfg, err := GetConfig() + require.NoError(t, err) + assert.Equal(t, RuntimeProfileServerDB, cfg.Runtime.Profile) + assert.True(t, cfg.Processes.Web) + assert.False(t, cfg.Processes.Worker) + assert.False(t, cfg.Processes.Scheduler) +} diff --git a/config/environments/dev.yaml b/config/environments/dev.yaml new file mode 100644 index 00000000..bfd288c7 --- /dev/null +++ b/config/environments/dev.yaml @@ -0,0 +1,4 @@ +app: + environment: "dev" +runtime: + profile: "server-db" diff --git a/config/environments/local.yaml b/config/environments/local.yaml new file mode 100644 index 00000000..931f7338 --- /dev/null +++ b/config/environments/local.yaml @@ -0,0 +1,4 @@ +app: + environment: "local" +runtime: + profile: "server-db" diff --git a/config/environments/prod.yaml b/config/environments/prod.yaml new file mode 100644 index 00000000..e22feaff --- /dev/null +++ b/config/environments/prod.yaml @@ -0,0 +1,4 @@ +app: + environment: "prod" +runtime: + profile: "distributed" diff --git a/config/environments/test.yaml b/config/environments/test.yaml new file mode 100644 index 00000000..d5a889c4 --- /dev/null +++ b/config/environments/test.yaml @@ -0,0 +1,4 @@ +app: + environment: "test" +runtime: + profile: "server-db" diff --git a/config/processes.yaml b/config/processes.yaml new file mode 100644 index 00000000..8b10e236 --- /dev/null +++ b/config/processes.yaml @@ -0,0 +1,18 @@ +profiles: + server-db: + web: true + worker: false + scheduler: false + coLocated: false + + single-node: + web: true + worker: true + scheduler: true + coLocated: true + + distributed: + web: true + worker: false + scheduler: false + coLocated: false diff --git a/docs/00-index.md b/docs/00-index.md index 5230263e..6a4d79f6 100644 --- a/docs/00-index.md +++ b/docs/00-index.md @@ -58,7 +58,9 @@ It is not intended as user-facing product documentation. - `pkg/repos/**/*.go` - `ent/schema/*.go` - `config/config.go` -- `config/config.yaml` +- `config/application.yaml` +- `config/environments/*.yaml` +- `config/processes.yaml` - `Makefile` - `build.mjs` - `package.json` diff --git a/docs/architecture/03-project-scope-analysis.md b/docs/architecture/03-project-scope-analysis.md index c9349399..a5ef8ab3 100644 --- a/docs/architecture/03-project-scope-analysis.md +++ b/docs/architecture/03-project-scope-analysis.md @@ -115,7 +115,7 @@ Build pipeline: Config loading: -- `config/config.go` + `config/config.yaml` +- `config/config.go` + layered YAML config (`application`, `environments/*`, `processes`) - Production can override via env vars (Viper env binding) Storage modes: diff --git a/docs/architecture/06-known-gaps-and-risks.md b/docs/architecture/06-known-gaps-and-risks.md index e386631c..092d794d 100644 --- a/docs/architecture/06-known-gaps-and-risks.md +++ b/docs/architecture/06-known-gaps-and-risks.md @@ -46,7 +46,7 @@ Impact: ## 5) Dev Runtime Drift Between Config and Docker Compose (Medium) -- Default config uses embedded SQLite (`config/config.yaml`). +- Default base config currently keeps embedded SQLite defaults in `config/application.yaml`. - Docker Compose currently starts Redis and Mailpit only; DB service is commented out. - Make targets include Postgres-dependent commands. @@ -77,4 +77,3 @@ Impact: 3. Re-enable or remove notification-center routes consistently. 4. Refresh e2e tests to match current GoShip flows. 5. Align local stack docs with actual DB mode and compose services. - diff --git a/pkg/services/docker_image_smoke_test.go b/pkg/services/docker_image_smoke_test.go new file mode 100644 index 00000000..23ae3054 --- /dev/null +++ b/pkg/services/docker_image_smoke_test.go @@ -0,0 +1,109 @@ +//go:build integration + +package services + +import ( + "context" + "fmt" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +func TestDockerImageBuildAndWebStartup(t *testing.T) { + if _, err := exec.LookPath("docker"); err != nil { + t.Skip("docker not available in PATH") + } + + repoRoot := mustRepoRoot(t) + imageTag := fmt.Sprintf("goship-integration-smoke:%d", time.Now().UnixNano()) + containerName := fmt.Sprintf("goship-smoke-%d", time.Now().UnixNano()) + + t.Cleanup(func() { + _ = runCommand(context.Background(), "docker", "rm", "-f", containerName) + _ = runCommand(context.Background(), "docker", "rmi", "-f", imageTag) + }) + + buildCtx, cancelBuild := context.WithTimeout(context.Background(), 12*time.Minute) + defer cancelBuild() + _, err := commandOutput(buildCtx, + "docker", "build", "--network", "host", "-t", imageTag, "-f", filepath.Join(repoRoot, "Dockerfile"), repoRoot, + ) + if err != nil { + if isTransientNetworkFailure(err.Error()) { + t.Skipf("skipping docker smoke test due to transient network failure: %v", err) + } + t.Fatalf("docker build failed: %v", err) + } + + runCtx, cancelRun := context.WithTimeout(context.Background(), time.Minute) + defer cancelRun() + if err := runCommand(runCtx, + "docker", "run", "-d", "--rm", "--name", containerName, + "-e", "PAGODA_APP_ENVIRONMENT=local", + imageTag, "web", + ); err != nil { + t.Fatalf("docker run failed: %v", err) + } + + deadline := time.Now().Add(20 * time.Second) + for time.Now().Before(deadline) { + running, err := commandOutput(context.Background(), "docker", "inspect", "-f", "{{.State.Running}}", containerName) + if err != nil { + break + } + if strings.TrimSpace(running) == "true" { + return + } + time.Sleep(500 * time.Millisecond) + } + + logs, _ := commandOutput(context.Background(), "docker", "logs", containerName) + t.Fatalf("container did not stay running; logs:\n%s", logs) +} + +func mustRepoRoot(t *testing.T) string { + t.Helper() + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("failed to resolve test file path") + } + return filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..")) +} + +func runCommand(ctx context.Context, name string, args ...string) error { + cmd := exec.CommandContext(ctx, name, args...) + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s %s: %w\n%s", name, strings.Join(args, " "), err, string(out)) + } + return nil +} + +func commandOutput(ctx context.Context, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("%s %s: %w\n%s", name, strings.Join(args, " "), err, string(out)) + } + return string(out), nil +} + +func isTransientNetworkFailure(msg string) bool { + probes := []string{ + "lookup storage.googleapis.com", + "i/o timeout", + "no such host", + "tls handshake timeout", + "connection reset by peer", + } + for _, p := range probes { + if strings.Contains(strings.ToLower(msg), strings.ToLower(p)) { + return true + } + } + return false +} From a773e818f816ca8aa12d07c43260cd8b66e24d81 Mon Sep 17 00:00:00 2001 From: Leo Audibert Date: Mon, 2 Mar 2026 21:30:44 -0800 Subject: [PATCH 015/291] refactor: reorganize goship app router and templ generation workflow --- LLM.txt | 23 +-- Makefile | 8 +- README.md | 4 +- app/goship/router.go | 12 ++ .../emails/{ => gen}/password_reset_templ.go | 2 +- .../registration_confirmation_templ.go | 2 +- .../subscription_confirmation_templ.go | 2 +- .../views/emails/{ => gen}/test_templ.go | 4 +- .../views/emails/{ => gen}/update_templ.go | 2 +- .../{ => web}/components/accordion.templ | 0 .../views/{ => web}/components/auth.templ | 0 .../{ => web}/components/bottom_nav.templ | 0 .../views/{ => web}/components/core.templ | 2 +- .../{ => web}/components/documentation.templ | 0 .../views/{ => web}/components/drawer.templ | 0 .../{ => web}/components/empty_page_msg.templ | 0 .../views/{ => web}/components/forms.templ | 0 .../components/gen}/accordion_templ.go | 6 +- .../components/gen}/auth_templ.go | 8 +- .../components/gen}/bottom_nav_templ.go | 18 +- .../components/gen}/core_templ.go | 48 ++--- .../components/gen}/documentation_templ.go | 6 +- .../components/gen}/drawer_templ.go | 36 ++-- .../components/gen}/empty_page_msg_templ.go | 6 +- .../components/gen}/forms_templ.go | 8 +- .../components/gen}/heatmap_templ.go | 4 +- .../components/gen}/icons_templ.go | 28 +-- .../components/gen}/loading_templ.go | 2 +- .../components/gen}/logos_templ.go | 30 +-- .../components/gen}/messages_templ.go | 8 +- .../components/gen}/navbar_templ.go | 48 ++--- .../components/gen}/payments_templ.go | 8 +- .../components/gen}/permissions_templ.go | 7 +- .../components/gen}/prev_nav_templ.go | 8 +- .../components/gen}/profile_templ.go | 46 ++--- .../components/gen}/pwa_install_templ.go | 6 +- .../components/gen}/sidebar_templ.go | 4 +- .../components/gen}/theme_toggle_templ.go | 4 +- .../components/gen}/tooltip_templ.go | 6 +- .../components/gen}/top_banner_templ.go | 2 +- .../views/{ => web}/components/heatmap.templ | 0 .../views/{ => web}/components/icons.templ | 0 .../views/{ => web}/components/loading.templ | 0 .../views/{ => web}/components/logos.templ | 0 .../views/{ => web}/components/messages.templ | 0 .../views/{ => web}/components/navbar.templ | 0 .../views/{ => web}/components/payments.templ | 0 .../{ => web}/components/permissions.templ | 0 .../views/{ => web}/components/prev_nav.templ | 0 .../views/{ => web}/components/profile.templ | 0 .../{ => web}/components/pwa_install.templ | 0 .../views/{ => web}/components/sidebar.templ | 0 .../{ => web}/components/theme_toggle.templ | 0 .../views/{ => web}/components/tooltip.templ | 0 .../{ => web}/components/top_banner.templ | 0 .../helpers/gen}/helpers_templ.go | 8 +- .../views/{ => web}/helpers/helpers.templ | 0 app/goship/views/{ => web}/layouts/auth.templ | 2 +- .../{ => web}/layouts/documentation.templ | 2 +- .../views/{ => web}/layouts/email.templ | 0 .../layouts/gen}/auth_templ.go | 12 +- .../layouts/gen}/documentation_templ.go | 6 +- .../layouts/gen}/email_templ.go | 2 +- .../layouts/gen}/landing_page_templ.go | 6 +- .../layouts/gen}/main_templ.go | 8 +- .../{ => web}/layouts/landing_page.templ | 2 +- app/goship/views/{ => web}/layouts/main.templ | 2 +- app/goship/views/{ => web}/pages/about.templ | 2 +- .../views/{ => web}/pages/contact.templ | 2 +- .../{ => web}/pages/delete_account.templ | 2 +- .../{ => web}/pages/docs_architecture.templ | 2 +- .../views/{ => web}/pages/docs_emails.templ | 2 +- .../views/{ => web}/pages/documentation.templ | 2 +- .../{ => web}/pages/email_subscribe.templ | 2 +- app/goship/views/{ => web}/pages/error.templ | 0 .../{ => web}/pages/forgot_password.templ | 2 +- .../{pages => web/pages/gen}/about_templ.go | 14 +- .../{pages => web/pages/gen}/contact_templ.go | 14 +- .../pages/gen}/delete_account_templ.go | 8 +- .../pages/gen}/docs_architecture_templ.go | 4 +- .../pages/gen}/docs_emails_templ.go | 4 +- .../pages/gen}/documentation_templ.go | 56 +++--- .../pages/gen}/email_subscribe_templ.go | 12 +- .../{pages => web/pages/gen}/error_templ.go | 24 +-- .../pages/gen}/forgot_password_templ.go | 12 +- .../pages/gen}/healthcheck_templ.go | 2 +- .../pages/gen}/home_feed_templ.go | 50 ++--- .../{pages => web/pages/gen}/home_templ.go | 16 +- .../pages/gen}/install_app_templ.go | 56 +++--- .../pages/gen}/invitations_templ.go | 4 +- .../pages/gen}/landing_page_templ.go | 46 ++--- .../{pages => web/pages/gen}/login_templ.go | 14 +- .../pages/gen}/notifications_templ.go | 41 ++-- .../pages/gen}/payments_templ.go | 28 +-- .../{pages => web/pages/gen}/phone_templ.go | 18 +- .../pages/gen}/preferences_templ.go | 52 ++--- .../pages/gen}/privacy_policy_templ.go | 60 +++--- .../{pages => web/pages/gen}/profile_templ.go | 4 +- .../pages/gen}/register_templ.go | 26 +-- .../pages/gen}/reset_password_templ.go | 12 +- .../views/{ => web}/pages/healthcheck.templ | 0 app/goship/views/{ => web}/pages/home.templ | 2 +- .../views/{ => web}/pages/home_feed.templ | 2 +- .../views/{ => web}/pages/install_app.templ | 2 +- .../views/{ => web}/pages/invitations.templ | 0 .../views/{ => web}/pages/landing_page.templ | 2 +- app/goship/views/{ => web}/pages/login.templ | 2 +- .../views/{ => web}/pages/notifications.templ | 2 +- .../views/{ => web}/pages/payments.templ | 2 +- app/goship/views/{ => web}/pages/phone.templ | 2 +- .../views/{ => web}/pages/preferences.templ | 2 +- .../{ => web}/pages/privacy_policy.templ | 2 +- .../views/{ => web}/pages/profile.templ | 2 +- .../views/{ => web}/pages/register.templ | 2 +- .../{ => web}/pages/reset_password.templ | 2 +- app/goship/web/routes/about.go | 4 +- app/goship/web/routes/contact.go | 4 +- app/goship/web/routes/delete_account.go | 4 +- app/goship/web/routes/docs.go | 4 +- app/goship/web/routes/email_subscribe.go | 6 +- app/goship/web/routes/error.go | 4 +- app/goship/web/routes/forgot_password.go | 6 +- app/goship/web/routes/healthcheck.go | 4 +- app/goship/web/routes/home.go | 4 +- app/goship/web/routes/home_feed.go | 4 +- app/goship/web/routes/install_app.go | 4 +- app/goship/web/routes/landing.go | 4 +- app/goship/web/routes/login.go | 4 +- app/goship/web/routes/notifications.go | 4 +- app/goship/web/routes/payments.go | 4 +- app/goship/web/routes/preferences.go | 4 +- app/goship/web/routes/privacy.go | 4 +- app/goship/web/routes/profile.go | 4 +- app/goship/web/routes/profile_photo.go | 2 +- app/goship/web/routes/push_notifs.go | 4 +- app/goship/web/routes/register.go | 6 +- app/goship/web/routes/reset_password.go | 4 +- app/goship/web/routes/upload_photo.go | 2 +- .../web/routes/verify_email_subscription.go | 2 +- cli/ship/cli.go | 180 ++++++++++++++++++ cli/ship/cli_test.go | 115 +++++++++++ cmd/web/main.go | 4 +- cmd/worker/main.go | 4 +- docs/00-index.md | 2 +- docs/architecture/01-architecture.md | 6 +- .../02-structure-and-boundaries.md | 2 +- docs/architecture/04-http-routes.md | 3 +- docs/guides/01-ai-agent-guide.md | 4 +- docs/reference/01-cli.md | 2 + pkg/controller/controller_test.go | 6 +- pkg/repos/emailsmanager/update_email.go | 4 +- pkg/services/cache_test.go | 4 + pkg/services/tasks_test.go | 4 + pkg/tasks/mail.go | 4 +- 154 files changed, 918 insertions(+), 599 deletions(-) create mode 100644 app/goship/router.go rename app/goship/views/emails/{ => gen}/password_reset_templ.go (99%) rename app/goship/views/emails/{ => gen}/registration_confirmation_templ.go (99%) rename app/goship/views/emails/{ => gen}/subscription_confirmation_templ.go (97%) rename app/goship/views/emails/{ => gen}/test_templ.go (91%) rename app/goship/views/emails/{ => gen}/update_templ.go (99%) rename app/goship/views/{ => web}/components/accordion.templ (100%) rename app/goship/views/{ => web}/components/auth.templ (100%) rename app/goship/views/{ => web}/components/bottom_nav.templ (100%) rename app/goship/views/{ => web}/components/core.templ (99%) rename app/goship/views/{ => web}/components/documentation.templ (100%) rename app/goship/views/{ => web}/components/drawer.templ (100%) rename app/goship/views/{ => web}/components/empty_page_msg.templ (100%) rename app/goship/views/{ => web}/components/forms.templ (100%) rename app/goship/views/{components => web/components/gen}/accordion_templ.go (91%) rename app/goship/views/{components => web/components/gen}/auth_templ.go (94%) rename app/goship/views/{components => web/components/gen}/bottom_nav_templ.go (93%) rename app/goship/views/{components => web/components/gen}/core_templ.go (91%) rename app/goship/views/{components => web/components/gen}/documentation_templ.go (92%) rename app/goship/views/{components => web/components/gen}/drawer_templ.go (94%) rename app/goship/views/{components => web/components/gen}/empty_page_msg_templ.go (90%) rename app/goship/views/{components => web/components/gen}/forms_templ.go (82%) rename app/goship/views/{components => web/components/gen}/heatmap_templ.go (95%) rename app/goship/views/{components => web/components/gen}/icons_templ.go (87%) rename app/goship/views/{components => web/components/gen}/loading_templ.go (99%) rename app/goship/views/{components => web/components/gen}/logos_templ.go (96%) rename app/goship/views/{components => web/components/gen}/messages_templ.go (95%) rename app/goship/views/{components => web/components/gen}/navbar_templ.go (90%) rename app/goship/views/{components => web/components/gen}/payments_templ.go (94%) rename app/goship/views/{components => web/components/gen}/permissions_templ.go (98%) rename app/goship/views/{components => web/components/gen}/prev_nav_templ.go (92%) rename app/goship/views/{components => web/components/gen}/profile_templ.go (90%) rename app/goship/views/{components => web/components/gen}/pwa_install_templ.go (96%) rename app/goship/views/{components => web/components/gen}/sidebar_templ.go (86%) rename app/goship/views/{components => web/components/gen}/theme_toggle_templ.go (93%) rename app/goship/views/{components => web/components/gen}/tooltip_templ.go (90%) rename app/goship/views/{components => web/components/gen}/top_banner_templ.go (98%) rename app/goship/views/{ => web}/components/heatmap.templ (100%) rename app/goship/views/{ => web}/components/icons.templ (100%) rename app/goship/views/{ => web}/components/loading.templ (100%) rename app/goship/views/{ => web}/components/logos.templ (100%) rename app/goship/views/{ => web}/components/messages.templ (100%) rename app/goship/views/{ => web}/components/navbar.templ (100%) rename app/goship/views/{ => web}/components/payments.templ (100%) rename app/goship/views/{ => web}/components/permissions.templ (100%) rename app/goship/views/{ => web}/components/prev_nav.templ (100%) rename app/goship/views/{ => web}/components/profile.templ (100%) rename app/goship/views/{ => web}/components/pwa_install.templ (100%) rename app/goship/views/{ => web}/components/sidebar.templ (100%) rename app/goship/views/{ => web}/components/theme_toggle.templ (100%) rename app/goship/views/{ => web}/components/tooltip.templ (100%) rename app/goship/views/{ => web}/components/top_banner.templ (100%) rename app/goship/views/{helpers => web/helpers/gen}/helpers_templ.go (92%) rename app/goship/views/{ => web}/helpers/helpers.templ (100%) rename app/goship/views/{ => web}/layouts/auth.templ (96%) rename app/goship/views/{ => web}/layouts/documentation.templ (93%) rename app/goship/views/{ => web}/layouts/email.templ (100%) rename app/goship/views/{layouts => web/layouts/gen}/auth_templ.go (90%) rename app/goship/views/{layouts => web/layouts/gen}/documentation_templ.go (95%) rename app/goship/views/{layouts => web/layouts/gen}/email_templ.go (97%) rename app/goship/views/{layouts => web/layouts/gen}/landing_page_templ.go (92%) rename app/goship/views/{layouts => web/layouts/gen}/main_templ.go (91%) rename app/goship/views/{ => web}/layouts/landing_page.templ (93%) rename app/goship/views/{ => web}/layouts/main.templ (96%) rename app/goship/views/{ => web}/pages/about.templ (94%) rename app/goship/views/{ => web}/pages/contact.templ (97%) rename app/goship/views/{ => web}/pages/delete_account.templ (97%) rename app/goship/views/{ => web}/pages/docs_architecture.templ (70%) rename app/goship/views/{ => web}/pages/docs_emails.templ (69%) rename app/goship/views/{ => web}/pages/documentation.templ (99%) rename app/goship/views/{ => web}/pages/email_subscribe.templ (99%) rename app/goship/views/{ => web}/pages/error.templ (100%) rename app/goship/views/{ => web}/pages/forgot_password.templ (96%) rename app/goship/views/{pages => web/pages/gen}/about_templ.go (89%) rename app/goship/views/{pages => web/pages/gen}/contact_templ.go (92%) rename app/goship/views/{pages => web/pages/gen}/delete_account_templ.go (95%) rename app/goship/views/{pages => web/pages/gen}/docs_architecture_templ.go (95%) rename app/goship/views/{pages => web/pages/gen}/docs_emails_templ.go (95%) rename app/goship/views/{pages => web/pages/gen}/documentation_templ.go (92%) rename app/goship/views/{pages => web/pages/gen}/email_subscribe_templ.go (88%) rename app/goship/views/{pages => web/pages/gen}/error_templ.go (88%) rename app/goship/views/{pages => web/pages/gen}/forgot_password_templ.go (90%) rename app/goship/views/{pages => web/pages/gen}/healthcheck_templ.go (98%) rename app/goship/views/{pages => web/pages/gen}/home_feed_templ.go (93%) rename app/goship/views/{pages => web/pages/gen}/home_templ.go (93%) rename app/goship/views/{pages => web/pages/gen}/install_app_templ.go (90%) rename app/goship/views/{pages => web/pages/gen}/invitations_templ.go (97%) rename app/goship/views/{pages => web/pages/gen}/landing_page_templ.go (92%) rename app/goship/views/{pages => web/pages/gen}/login_templ.go (93%) rename app/goship/views/{pages => web/pages/gen}/notifications_templ.go (89%) rename app/goship/views/{pages => web/pages/gen}/payments_templ.go (90%) rename app/goship/views/{pages => web/pages/gen}/phone_templ.go (93%) rename app/goship/views/{pages => web/pages/gen}/preferences_templ.go (81%) rename app/goship/views/{pages => web/pages/gen}/privacy_policy_templ.go (84%) rename app/goship/views/{pages => web/pages/gen}/profile_templ.go (95%) rename app/goship/views/{pages => web/pages/gen}/register_templ.go (90%) rename app/goship/views/{pages => web/pages/gen}/reset_password_templ.go (92%) rename app/goship/views/{ => web}/pages/healthcheck.templ (100%) rename app/goship/views/{ => web}/pages/home.templ (97%) rename app/goship/views/{ => web}/pages/home_feed.templ (99%) rename app/goship/views/{ => web}/pages/install_app.templ (99%) rename app/goship/views/{ => web}/pages/invitations.templ (100%) rename app/goship/views/{ => web}/pages/landing_page.templ (99%) rename app/goship/views/{ => web}/pages/login.templ (98%) rename app/goship/views/{ => web}/pages/notifications.templ (98%) rename app/goship/views/{ => web}/pages/payments.templ (98%) rename app/goship/views/{ => web}/pages/phone.templ (98%) rename app/goship/views/{ => web}/pages/preferences.templ (99%) rename app/goship/views/{ => web}/pages/privacy_policy.templ (98%) rename app/goship/views/{ => web}/pages/profile.templ (87%) rename app/goship/views/{ => web}/pages/register.templ (98%) rename app/goship/views/{ => web}/pages/reset_password.templ (97%) diff --git a/LLM.txt b/LLM.txt index 6b2f4875..7a4c3281 100644 --- a/LLM.txt +++ b/LLM.txt @@ -159,7 +159,7 @@ For in-depth info on the architecture of the project, please see the [mikestefan The most important aspects to note are: - The `Container` struct is instantiated when the app starts up and is used to pass dependencies around the app, specifically core services like `Logger`, `Database`, `ORM`, `Cache`, etc. -- Routes are defined in `app/goship/web/routes/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/pages/` that represents the view. +- Routes are defined in `app/goship/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/web/pages/` that represents the view. ## Database @@ -529,7 +529,7 @@ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/app/goship/views/components" + "github.com/mikestefanello/pagoda/app/goship/views/web/components" ) templ PostsIndex(page *controller.Page) { @@ -623,7 +623,7 @@ It is not intended as user-facing product documentation. - `cli/ship/cmd/ship/main.go` - `cli/ship/cli.go` - `pkg/services/container.go` -- `app/goship/web/routes/router.go` +- `app/goship/router.go` - `app/goship/web/routes/*.go` - `pkg/tasks/*.go` - `pkg/repos/**/*.go` @@ -659,7 +659,7 @@ The application follows a layered structure: ## Web Runtime Flow 1. `cmd/web/main.go` creates container via `services.NewContainer()`. -2. `routes.BuildRouter(c)` configures middleware stack, registers routes, and returns an error on startup misconfiguration. +2. `goship.BuildRouter(c)` is the canonical app router entrypoint and delegates to modular route composition. 3. Echo server starts with request timeout middleware (SSE-aware). 4. Request path executes middleware chain, route handler, and page rendering. @@ -722,8 +722,8 @@ The UI is server-rendered using Templ components. - Base page abstraction: `pkg/controller/page.go` - Render orchestration: `pkg/controller/controller.go` -- Layout wrappers: `app/goship/views/layouts/*.templ` -- Route page components: `app/goship/views/pages/*.templ` +- Layout wrappers: `app/goship/views/web/layouts/*.templ` +- Route page components: `app/goship/views/web/pages/*.templ` HTMX behavior is integrated in the page object (`Page.HTMX`) and controller render logic. @@ -795,7 +795,7 @@ App web code is now app-scoped: Router source of truth: -- `app/goship/web/routes/router.go` +- `app/goship/router.go` ## Refactor Status @@ -963,7 +963,7 @@ FILE: docs/architecture/04-http-routes.md --- # HTTP Route Map -Routes are registered in `app/goship/web/routes/router.go`. +Routes are wired through canonical `app/goship/router.go` and composed in `app/goship/web/routes/router.go`. ## Public/General Routes @@ -1077,7 +1077,6 @@ Notification center routes have implementations but are commented out in route w - delete notification - mark read/unread endpoints - --- FILE: docs/architecture/05-data-model.md --- @@ -1261,7 +1260,7 @@ This guide is for code agents making changes in this repository. ## Start Here 1. Read `docs/architecture/03-project-scope-analysis.md` and `docs/architecture/06-known-gaps-and-risks.md`. -2. Inspect route wiring in `app/goship/web/routes/router.go` before editing handlers. +2. Inspect route wiring in `app/goship/router.go` before editing handlers. 3. Inspect `pkg/services/container.go` before assuming a dependency is initialized. ## Architectural Conventions @@ -1303,7 +1302,7 @@ Dependency wiring: Routing and middleware: -- `app/goship/web/routes/router.go` +- `app/goship/router.go` - `pkg/middleware/*.go` Data and domain: @@ -1634,6 +1633,7 @@ Database: Generation: +- `ship templ generate [--path ] [--file ]` - `ship generate ` (planned) - `ship destroy ` (planned) @@ -1657,6 +1657,7 @@ These commands are implemented as wrappers over existing workflows: - `ship db migrate` -> `make migrate` - `ship db rollback [amount]` -> `atlas migrate down ... [amount]` - `ship db seed` -> `make seed` +- `ship templ generate --path app` -> `templ generate -path app`, then move each `*_templ.go` into sibling `gen/` directory Local run examples from repository root: diff --git a/Makefile b/Makefile index 9ddaef8a..bab8e0ef 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,10 @@ hooks: ## Install git hooks via lefthook llm-txt: ## Generate root LLM.txt from README and docs markdown files bash scripts/generate-llm-txt.sh +.PHONY: templ-gen +templ-gen: ## Generate templ code next to .templ files via ship CLI + go run ./cli/ship/cmd/ship templ generate --path app + # Core workflow ------------------------------------------------------------------------------ .PHONY: dev @@ -151,8 +155,8 @@ ent-gen: ## Generate Ent code ent-new: ## Create a new Ent entity go run entgo.io/ent/cmd/ent new $(name) - .PHONY: generate -generate: ## Run code generation +.PHONY: generate +generate: templ-gen ## Run code generation go generate ./... .PHONY: up diff --git a/README.md b/README.md index 6ef52fee..5827a04f 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ For in-depth info on the architecture of the project, please see the [mikestefan The most important aspects to note are: - The `Container` struct is instantiated when the app starts up and is used to pass dependencies around the app, specifically core services like `Logger`, `Database`, `ORM`, `Cache`, etc. -- Routes are defined in `app/goship/web/routes/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/pages/` that represents the view. +- Routes are defined in `app/goship/router.go` and are registered to the `Echo` framework. Generally, any logic that alters the DB should be done in the `repos` layer so that it is easily testable, and can be used by other routes. A route will generally have a `Component`, which is a Templ component defined in `app/goship/views/web/pages/` that represents the view. ## Database @@ -521,7 +521,7 @@ package pages import ( "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/types" - "github.com/mikestefanello/pagoda/app/goship/views/components" + "github.com/mikestefanello/pagoda/app/goship/views/web/components" ) templ PostsIndex(page *controller.Page) { diff --git a/app/goship/router.go b/app/goship/router.go new file mode 100644 index 00000000..7cf22482 --- /dev/null +++ b/app/goship/router.go @@ -0,0 +1,12 @@ +package goship + +import ( + "github.com/mikestefanello/pagoda/app/goship/web/routes" + "github.com/mikestefanello/pagoda/pkg/services" +) + +// BuildRouter is the canonical app-level router entrypoint. +// Detailed domain route composition lives under app/goship/web/routes. +func BuildRouter(c *services.Container) error { + return routes.BuildRouter(c) +} diff --git a/app/goship/views/emails/password_reset_templ.go b/app/goship/views/emails/gen/password_reset_templ.go similarity index 99% rename from app/goship/views/emails/password_reset_templ.go rename to app/goship/views/emails/gen/password_reset_templ.go index 56b2bfcc..1c0a6e1f 100644 --- a/app/goship/views/emails/password_reset_templ.go +++ b/app/goship/views/emails/gen/password_reset_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package emails //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/app/goship/views/emails/registration_confirmation_templ.go b/app/goship/views/emails/gen/registration_confirmation_templ.go similarity index 99% rename from app/goship/views/emails/registration_confirmation_templ.go rename to app/goship/views/emails/gen/registration_confirmation_templ.go index 7bbfc23e..39aeacf0 100644 --- a/app/goship/views/emails/registration_confirmation_templ.go +++ b/app/goship/views/emails/gen/registration_confirmation_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package emails //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/app/goship/views/emails/subscription_confirmation_templ.go b/app/goship/views/emails/gen/subscription_confirmation_templ.go similarity index 97% rename from app/goship/views/emails/subscription_confirmation_templ.go rename to app/goship/views/emails/gen/subscription_confirmation_templ.go index 8a8e2856..ff3af630 100644 --- a/app/goship/views/emails/subscription_confirmation_templ.go +++ b/app/goship/views/emails/gen/subscription_confirmation_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package emails //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/app/goship/views/emails/test_templ.go b/app/goship/views/emails/gen/test_templ.go similarity index 91% rename from app/goship/views/emails/test_templ.go rename to app/goship/views/emails/gen/test_templ.go index 2612b9a5..4c67c2c0 100644 --- a/app/goship/views/emails/test_templ.go +++ b/app/goship/views/emails/gen/test_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package emails //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -32,7 +32,7 @@ func TestEmail() templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("Test email template. See services/mail.go to provide your implementation.") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/emails/test.templ`, Line: 4, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/emails/test.templ`, Line: 4, Col: 81} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/emails/update_templ.go b/app/goship/views/emails/gen/update_templ.go similarity index 99% rename from app/goship/views/emails/update_templ.go rename to app/goship/views/emails/gen/update_templ.go index 1c0c3345..eb3031eb 100644 --- a/app/goship/views/emails/update_templ.go +++ b/app/goship/views/emails/gen/update_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package emails //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/app/goship/views/components/accordion.templ b/app/goship/views/web/components/accordion.templ similarity index 100% rename from app/goship/views/components/accordion.templ rename to app/goship/views/web/components/accordion.templ diff --git a/app/goship/views/components/auth.templ b/app/goship/views/web/components/auth.templ similarity index 100% rename from app/goship/views/components/auth.templ rename to app/goship/views/web/components/auth.templ diff --git a/app/goship/views/components/bottom_nav.templ b/app/goship/views/web/components/bottom_nav.templ similarity index 100% rename from app/goship/views/components/bottom_nav.templ rename to app/goship/views/web/components/bottom_nav.templ diff --git a/app/goship/views/components/core.templ b/app/goship/views/web/components/core.templ similarity index 99% rename from app/goship/views/components/core.templ rename to app/goship/views/web/components/core.templ index 1773ae18..e98f11e8 100644 --- a/app/goship/views/components/core.templ +++ b/app/goship/views/web/components/core.templ @@ -4,7 +4,7 @@ import ( "fmt" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" - "github.com/mikestefanello/pagoda/app/goship/views/helpers" + "github.com/mikestefanello/pagoda/app/goship/views/web/helpers" "strings" ) diff --git a/app/goship/views/components/documentation.templ b/app/goship/views/web/components/documentation.templ similarity index 100% rename from app/goship/views/components/documentation.templ rename to app/goship/views/web/components/documentation.templ diff --git a/app/goship/views/components/drawer.templ b/app/goship/views/web/components/drawer.templ similarity index 100% rename from app/goship/views/components/drawer.templ rename to app/goship/views/web/components/drawer.templ diff --git a/app/goship/views/components/empty_page_msg.templ b/app/goship/views/web/components/empty_page_msg.templ similarity index 100% rename from app/goship/views/components/empty_page_msg.templ rename to app/goship/views/web/components/empty_page_msg.templ diff --git a/app/goship/views/components/forms.templ b/app/goship/views/web/components/forms.templ similarity index 100% rename from app/goship/views/components/forms.templ rename to app/goship/views/web/components/forms.templ diff --git a/app/goship/views/components/accordion_templ.go b/app/goship/views/web/components/gen/accordion_templ.go similarity index 91% rename from app/goship/views/components/accordion_templ.go rename to app/goship/views/web/components/gen/accordion_templ.go index 5e981543..276dbe56 100644 --- a/app/goship/views/components/accordion_templ.go +++ b/app/goship/views/web/components/gen/accordion_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -38,7 +38,7 @@ func AccordionItem(title string, expanded bool) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("{ expanded: %v }", expanded)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/accordion.templ`, Line: 8, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/accordion.templ`, Line: 8, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -51,7 +51,7 @@ func AccordionItem(title string, expanded bool) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/accordion.templ`, Line: 14, Col: 10} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/accordion.templ`, Line: 14, Col: 10} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/auth_templ.go b/app/goship/views/web/components/gen/auth_templ.go similarity index 94% rename from app/goship/views/components/auth_templ.go rename to app/goship/views/web/components/gen/auth_templ.go index a802c451..fcf33be8 100644 --- a/app/goship/views/components/auth_templ.go +++ b/app/goship/views/web/components/gen/auth_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameLogin)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 23, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/auth.templ`, Line: 23, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -65,7 +65,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRegister)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 40, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/auth.templ`, Line: 40, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -84,7 +84,7 @@ func AuthButtons(page *controller.Page, showLoginBtn, showRegisterBtn, showReset var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameForgotPassword)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/auth.templ`, Line: 61, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/auth.templ`, Line: 61, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/bottom_nav_templ.go b/app/goship/views/web/components/gen/bottom_nav_templ.go similarity index 93% rename from app/goship/views/components/bottom_nav_templ.go rename to app/goship/views/web/components/gen/bottom_nav_templ.go index 7124fcfb..a7d40a36 100644 --- a/app/goship/views/components/bottom_nav_templ.go +++ b/app/goship/views/web/components/gen/bottom_nav_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -39,7 +39,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(page.ShowBottomNavbar)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 9, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 9, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -57,7 +57,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 12, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 12, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -84,7 +84,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -125,7 +125,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 39, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 39, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -138,7 +138,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotifications")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 70, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 70, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -151,7 +151,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 100, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 100, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -174,7 +174,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNamePreferences)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 117, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 117, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -187,7 +187,7 @@ func BottomNav(page *controller.Page) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameProfile)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/bottom_nav.templ`, Line: 148, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/bottom_nav.templ`, Line: 148, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/core_templ.go b/app/goship/views/web/components/gen/core_templ.go similarity index 91% rename from app/goship/views/components/core_templ.go rename to app/goship/views/web/components/gen/core_templ.go index 3f8c8391..d27a4aa4 100644 --- a/app/goship/views/components/core_templ.go +++ b/app/goship/views/web/components/gen/core_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import ( "fmt" - "github.com/mikestefanello/pagoda/app/goship/views/helpers" + "github.com/mikestefanello/pagoda/app/goship/views/web/helpers/gen" "github.com/mikestefanello/pagoda/pkg/controller" "github.com/mikestefanello/pagoda/pkg/routing/routenames" "strings" @@ -44,7 +44,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 13, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 13, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -58,7 +58,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("| %s", page.Title)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 15, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 15, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -69,10 +69,10 @@ func Metatags(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("favicon.png")) + var templ_7745c5c3_Var4 templ.SafeURL + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(helpers.File("favicon.png")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 18, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 18, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -90,7 +90,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(page.Metatags.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 23, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 23, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func Metatags(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(page.Metatags.Keywords, ", ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 26, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 26, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -153,10 +153,10 @@ func CSS() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("svelte_bundle.css")) + var templ_7745c5c3_Var8 templ.SafeURL + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(helpers.File("svelte_bundle.css")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 40, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 40, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -166,10 +166,10 @@ func CSS() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("styles_bundle.css")) + var templ_7745c5c3_Var9 templ.SafeURL + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(helpers.File("styles_bundle.css")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 41, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 41, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -179,10 +179,10 @@ func CSS() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("manifest.json")) + var templ_7745c5c3_Var10 templ.SafeURL + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(helpers.File("manifest.json")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 42, Col: 88} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 42, Col: 88} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -224,7 +224,7 @@ func JS() templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(helpers.File("icon.png"))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 74, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 74, Col: 94} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func JS() templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("main.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 83, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 83, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -250,7 +250,7 @@ func JS() templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.File("vanilla_bundle.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 84, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 84, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -263,7 +263,7 @@ func JS() templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helpers.ServiceWorkerFile("service-worker.js")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 87, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 87, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -482,7 +482,7 @@ func TextFooter(page *controller.Page) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameAboutUs)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 217, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 217, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -495,7 +495,7 @@ func TextFooter(page *controller.Page) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNamePrivacyPolicy)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/core.templ`, Line: 225, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/core.templ`, Line: 225, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/documentation_templ.go b/app/goship/views/web/components/gen/documentation_templ.go similarity index 92% rename from app/goship/views/components/documentation_templ.go rename to app/goship/views/web/components/gen/documentation_templ.go index 1e7b25fc..7c8c3434 100644 --- a/app/goship/views/components/documentation_templ.go +++ b/app/goship/views/web/components/gen/documentation_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -36,7 +36,7 @@ func SectionTitle(title string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/documentation.templ`, Line: 4, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/documentation.templ`, Line: 4, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -78,7 +78,7 @@ func SubSectionTitle(title string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/documentation.templ`, Line: 8, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/documentation.templ`, Line: 8, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/drawer_templ.go b/app/goship/views/web/components/gen/drawer_templ.go similarity index 94% rename from app/goship/views/components/drawer_templ.go rename to app/goship/views/web/components/gen/drawer_templ.go index 4a7bf010..4628c653 100644 --- a/app/goship/views/components/drawer_templ.go +++ b/app/goship/views/web/components/gen/drawer_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func Drawer(page *controller.Page, showTopBar bool) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 29, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 29, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func drawerButtonToggle(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 68, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 68, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -154,7 +154,7 @@ func drawerPanel(page *controller.Page, showTopBar bool) templ.Component { templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -416,7 +416,7 @@ func drawerTopBar(page *controller.Page) templ.Component { var templ_7745c5c3_Var11 templ.SafeURL templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameHomeFeed))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 147, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 147, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -434,7 +434,7 @@ func drawerTopBar(page *controller.Page) templ.Component { var templ_7745c5c3_Var12 templ.SafeURL templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 149, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 149, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -452,13 +452,13 @@ func drawerTopBar(page *controller.Page) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(page.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 161, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 161, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -496,7 +496,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUserProfilePicURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 182, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 182, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -514,7 +514,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 185, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 185, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -527,7 +527,7 @@ func profile(page *controller.Page) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 186, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 186, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -599,7 +599,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 202, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 202, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -612,7 +612,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotifications")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 214, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 214, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -625,7 +625,7 @@ func SidebarSharedFields(page *controller.Page) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 224, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 224, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -685,7 +685,7 @@ func dropdownEntry(icon templ.Component, menuTitle string) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(menuTitle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 252, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 252, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -751,7 +751,7 @@ func dropdownListEntryWithIcon(icon templ.Component, url, menuTitle string) temp var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(menuTitle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 276, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 276, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -811,7 +811,7 @@ func dropdownListEntry(url, menuTitle string) templ.Component { var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(menuTitle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 282, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 282, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -859,7 +859,7 @@ func _dropdownListEntry(url string) templ.Component { var templ_7745c5c3_Var32 templ.SafeURL templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(url)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/drawer.templ`, Line: 289, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/drawer.templ`, Line: 289, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/empty_page_msg_templ.go b/app/goship/views/web/components/gen/empty_page_msg_templ.go similarity index 90% rename from app/goship/views/components/empty_page_msg_templ.go rename to app/goship/views/web/components/gen/empty_page_msg_templ.go index 06b8e2e8..fd2967c9 100644 --- a/app/goship/views/components/empty_page_msg_templ.go +++ b/app/goship/views/web/components/gen/empty_page_msg_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -44,7 +44,7 @@ func EmptyPageMessage(message, styleClasses string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/empty_page_msg.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/empty_page_msg.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -57,7 +57,7 @@ func EmptyPageMessage(message, styleClasses string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(message) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/empty_page_msg.templ`, Line: 10, Col: 11} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/empty_page_msg.templ`, Line: 10, Col: 11} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/forms_templ.go b/app/goship/views/web/components/gen/forms_templ.go similarity index 82% rename from app/goship/views/components/forms_templ.go rename to app/goship/views/web/components/gen/forms_templ.go index 53775a8f..131fffb3 100644 --- a/app/goship/views/components/forms_templ.go +++ b/app/goship/views/web/components/gen/forms_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -36,7 +36,7 @@ func FormCSRF(token string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(token) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/forms.templ`, Line: 4, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/forms.templ`, Line: 4, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -72,14 +72,14 @@ func FormFieldErrors(errs []string) templ.Component { } ctx = templ.ClearChildren(ctx) for _, err := range errs { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(err) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/forms.templ`, Line: 23, Col: 8} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/forms.templ`, Line: 23, Col: 8} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/heatmap_templ.go b/app/goship/views/web/components/gen/heatmap_templ.go similarity index 95% rename from app/goship/views/components/heatmap_templ.go rename to app/goship/views/web/components/gen/heatmap_templ.go index 2e85692a..9ac14eab 100644 --- a/app/goship/views/components/heatmap_templ.go +++ b/app/goship/views/web/components/gen/heatmap_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -38,7 +38,7 @@ func HeatmapComponent(countsByDay []types.CountByDay) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/app/goship/views/components/icons_templ.go b/app/goship/views/web/components/gen/icons_templ.go similarity index 87% rename from app/goship/views/components/icons_templ.go rename to app/goship/views/web/components/gen/icons_templ.go index c2eaeb98..5a37d892 100644 --- a/app/goship/views/components/icons_templ.go +++ b/app/goship/views/web/components/gen/icons_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -41,7 +41,7 @@ func TechIcon(styleClasses, websiteUrl, iconUrl, altText string) templ.Component var templ_7745c5c3_Var3 templ.SafeURL templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(websiteUrl)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 4, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 4, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -54,7 +54,7 @@ func TechIcon(styleClasses, websiteUrl, iconUrl, altText string) templ.Component var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -76,7 +76,7 @@ func TechIcon(styleClasses, websiteUrl, iconUrl, altText string) templ.Component var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -89,7 +89,7 @@ func TechIcon(styleClasses, websiteUrl, iconUrl, altText string) templ.Component var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(iconUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 7, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 7, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -102,7 +102,7 @@ func TechIcon(styleClasses, websiteUrl, iconUrl, altText string) templ.Component var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(altText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 8, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 8, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -149,7 +149,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var11 templ.SafeURL templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(websiteUrl)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 15, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 15, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -162,7 +162,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -184,7 +184,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var13).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -197,7 +197,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(lightModeIconUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 18, Col: 25} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 18, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -210,7 +210,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(altText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 19, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 19, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -232,7 +232,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -245,7 +245,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(darkModeIconUrl) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 24, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 24, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -258,7 +258,7 @@ func TechIconWithDarkAndLightModes(styleClasses, websiteUrl, lightModeIconUrl, d var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(altText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/icons.templ`, Line: 25, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/icons.templ`, Line: 25, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/loading_templ.go b/app/goship/views/web/components/gen/loading_templ.go similarity index 99% rename from app/goship/views/components/loading_templ.go rename to app/goship/views/web/components/gen/loading_templ.go index bf66e662..8cc0bd01 100644 --- a/app/goship/views/components/loading_templ.go +++ b/app/goship/views/web/components/gen/loading_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/app/goship/views/components/logos_templ.go b/app/goship/views/web/components/gen/logos_templ.go similarity index 96% rename from app/goship/views/components/logos_templ.go rename to app/goship/views/web/components/gen/logos_templ.go index c1ca10bc..4df037fe 100644 --- a/app/goship/views/components/logos_templ.go +++ b/app/goship/views/web/components/gen/logos_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -41,7 +41,7 @@ func TailwindLogo(height string) templ.Component { var templ_7745c5c3_Var3 templ.SafeURL templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs("https://tailwindcss.com/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 4, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 4, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -54,7 +54,7 @@ func TailwindLogo(height string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -76,7 +76,7 @@ func TailwindLogo(height string) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -123,7 +123,7 @@ func AlpineLogo(height string) templ.Component { var templ_7745c5c3_Var9 templ.SafeURL templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs("https://alpinejs.dev/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 13, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 13, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -136,7 +136,7 @@ func AlpineLogo(height string) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -158,7 +158,7 @@ func AlpineLogo(height string) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -200,7 +200,7 @@ func RedisLogo() templ.Component { var templ_7745c5c3_Var14 templ.SafeURL templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs("https://github.com/redis/redis") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 23, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 23, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -387,7 +387,7 @@ func PostgresLogo() templ.Component { var templ_7745c5c3_Var21 templ.SafeURL templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs("https://www.postgresql.org/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 49, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 49, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -463,7 +463,7 @@ func StripeLogo(height string) templ.Component { var templ_7745c5c3_Var25 templ.SafeURL templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs("https://stripe.com/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 59, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 59, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -476,7 +476,7 @@ func StripeLogo(height string) templ.Component { var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var24).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -498,7 +498,7 @@ func StripeLogo(height string) templ.Component { var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var27).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -540,7 +540,7 @@ func SupabaseLogo() templ.Component { var templ_7745c5c3_Var30 templ.SafeURL templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs("https://supabase.com/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 65, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 65, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -582,7 +582,7 @@ func GoLogo(classes string) templ.Component { var templ_7745c5c3_Var32 templ.SafeURL templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs("https://go.dev/") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 90, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 90, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { @@ -604,7 +604,7 @@ func GoLogo(classes string) templ.Component { var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var33).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/logos.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/logos.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/messages_templ.go b/app/goship/views/web/components/gen/messages_templ.go similarity index 95% rename from app/goship/views/components/messages_templ.go rename to app/goship/views/web/components/gen/messages_templ.go index 78379230..f713cde6 100644 --- a/app/goship/views/components/messages_templ.go +++ b/app/goship/views/web/components/gen/messages_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -143,7 +143,7 @@ func StyledMessage(msgType, color, text, bgColorClass, contentColorClass string) var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/messages.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/messages.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -156,7 +156,7 @@ func StyledMessage(msgType, color, text, bgColorClass, contentColorClass string) var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(msgType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/messages.templ`, Line: 54, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/messages.templ`, Line: 54, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -186,7 +186,7 @@ func StyledMessage(msgType, color, text, bgColorClass, contentColorClass string) var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/messages.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/messages.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/navbar_templ.go b/app/goship/views/web/components/gen/navbar_templ.go similarity index 90% rename from app/goship/views/components/navbar_templ.go rename to app/goship/views/web/components/gen/navbar_templ.go index 7b1f0013..2a9c3495 100644 --- a/app/goship/views/components/navbar_templ.go +++ b/app/goship/views/web/components/gen/navbar_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameRealtime)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 13, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 13, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -64,7 +64,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 templ.SafeURL templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 23, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 23, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -77,7 +77,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.AppName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 25, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 25, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -101,7 +101,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameHomeFeed)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 36, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 36, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -119,7 +119,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameAboutUs)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 46, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 46, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -132,7 +132,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs("Contact Us") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 53, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 53, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -151,7 +151,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var8 templ.SafeURL templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 58, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 58, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -186,7 +186,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotifications")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 82, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 82, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -199,7 +199,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("normalNotificationsCount")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 95, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 95, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -229,7 +229,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -247,7 +247,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUserProfilePicURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 123, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 123, Col: 76} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -265,7 +265,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 140, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 140, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -278,7 +278,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(page.AuthUser.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 143, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 143, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -296,7 +296,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameProfile)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 151, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 151, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -314,7 +314,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNamePreferences)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 159, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 159, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -327,7 +327,7 @@ func Navbar(page *controller.Page) templ.Component { var templ_7745c5c3_Var18 templ.SafeURL templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLogout))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 166, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 166, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -392,7 +392,7 @@ func LogInButton(page *controller.Page, btnClasses string) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -405,7 +405,7 @@ func LogInButton(page *controller.Page, btnClasses string) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameLogin)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 188, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 188, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -447,7 +447,7 @@ func HomeButton(page *controller.Page, btnClasses string) templ.Component { var templ_7745c5c3_Var24 templ.SafeURL templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 208, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 208, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -469,7 +469,7 @@ func HomeButton(page *controller.Page, btnClasses string) templ.Component { var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var25).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -511,13 +511,13 @@ func FloatingActionButton(page *controller.Page) templ.Component { var templ_7745c5c3_Var28 templ.SafeURL templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLandingPage))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 226, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 226, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -553,7 +553,7 @@ func DocsButton(page *controller.Page) templ.Component { var templ_7745c5c3_Var30 templ.SafeURL templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameDocs))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/navbar.templ`, Line: 245, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/navbar.templ`, Line: 245, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/payments_templ.go b/app/goship/views/web/components/gen/payments_templ.go similarity index 94% rename from app/goship/views/components/payments_templ.go rename to app/goship/views/web/components/gen/payments_templ.go index c57fb5d4..0ab57ec0 100644 --- a/app/goship/views/components/payments_templ.go +++ b/app/goship/views/web/components/gen/payments_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -82,7 +82,7 @@ func PricingPage(page *controller.Page) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNamePricingPage)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/payments.templ`, Line: 20, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/payments.templ`, Line: 20, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -124,7 +124,7 @@ func StripePortal(page *controller.Page) templ.Component { var templ_7745c5c3_Var5 templ.SafeURL templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(page.ToURL(routenames.RouteNameCreatePortalSession))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/payments.templ`, Line: 34, Col: 96} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/payments.templ`, Line: 34, Col: 96} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -138,7 +138,7 @@ func StripePortal(page *controller.Page) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/app/goship/views/components/permissions_templ.go b/app/goship/views/web/components/gen/permissions_templ.go similarity index 98% rename from app/goship/views/components/permissions_templ.go rename to app/goship/views/web/components/gen/permissions_templ.go index 0f78fa2a..685a3321 100644 --- a/app/goship/views/components/permissions_templ.go +++ b/app/goship/views/web/components/gen/permissions_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -98,8 +98,7 @@ func PlatformPermissionButton(platformName domain.NotificationPlatform, permissi templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var3 = []any{ - "p-2 m-2 text-sm font-medium focus:outline-none rounded-full", + var templ_7745c5c3_Var3 = []any{"p-2 m-2 text-sm font-medium focus:outline-none rounded-full", templ.KV("text-gray-900 bg-slate-200 hover:bg-blue-200 hover:text-blue-800 dark:bg-gray-700 dark:text-gray-300 dark:hover:text-white dark:hover:bg-blue-700", permissionGranted), templ.KV("text-gray-600 bg-slate-200 hover:bg-blue-200 hover:text-blue-800 dark:bg-gray-700 dark:text-gray-400 dark:hover:text-white dark:hover:bg-blue-700", !permissionGranted), } @@ -114,7 +113,7 @@ func PlatformPermissionButton(platformName domain.NotificationPlatform, permissi var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/permissions.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/permissions.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/prev_nav_templ.go b/app/goship/views/web/components/gen/prev_nav_templ.go similarity index 92% rename from app/goship/views/components/prev_nav_templ.go rename to app/goship/views/web/components/gen/prev_nav_templ.go index 82aadcb1..7379a2fa 100644 --- a/app/goship/views/components/prev_nav_templ.go +++ b/app/goship/views/web/components/gen/prev_nav_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -41,7 +41,7 @@ func PrevNavBarWithTitle(prevURL, avatarURL, title string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(prevURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/prev_nav.templ`, Line: 13, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/prev_nav.templ`, Line: 13, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -69,7 +69,7 @@ func PrevNavBarWithTitle(prevURL, avatarURL, title string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(avatarURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/prev_nav.templ`, Line: 32, Col: 25} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/prev_nav.templ`, Line: 32, Col: 25} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -87,7 +87,7 @@ func PrevNavBarWithTitle(prevURL, avatarURL, title string) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/prev_nav.templ`, Line: 36, Col: 11} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/prev_nav.templ`, Line: 36, Col: 11} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/profile_templ.go b/app/goship/views/web/components/gen/profile_templ.go similarity index 90% rename from app/goship/views/components/profile_templ.go rename to app/goship/views/web/components/gen/profile_templ.go index 349d48ce..7fc55c8d 100644 --- a/app/goship/views/components/profile_templ.go +++ b/app/goship/views/web/components/gen/profile_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -47,7 +47,7 @@ func Profile(page *controller.Page, profile domain.Profile, isSelf, isPotentialM var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(profile.Bio) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 13, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 13, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -75,7 +75,7 @@ func Profile(page *controller.Page, profile domain.Profile, isSelf, isPotentialM var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("uploadPhoto.delete", image.ID) + "?csrf=" + page.CSRF) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 29, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 29, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -93,7 +93,7 @@ func Profile(page *controller.Page, profile domain.Profile, isSelf, isPotentialM var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(image.FullURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 48, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 48, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -106,7 +106,7 @@ func Profile(page *controller.Page, profile domain.Profile, isSelf, isPotentialM var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(image.Alt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 49, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 49, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -261,7 +261,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -279,7 +279,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameProfile, profile.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 157, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 157, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -302,7 +302,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(profile.ProfileImage.ThumbnailURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 166, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 166, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -349,7 +349,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -367,7 +367,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameProfile, profile.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 184, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 184, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -385,7 +385,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(profile.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 191, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 191, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -408,7 +408,7 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("{ tooltip: 'Only %d gallery pictures allowed' }", galleryPicsMaxCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 199, Col: 103} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 199, Col: 103} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -435,13 +435,13 @@ func ProfileHeader(page *controller.Page, profile *domain.Profile, isSelf, isPot var templ_7745c5c3_Var16 templ.SafeURL templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(page.ToURL(routenames.RouteNameLogout))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 207, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 207, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -482,7 +482,7 @@ func showContactInfo(profile domain.Profile) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(profile.CountryCode) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 231, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 231, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -496,7 +496,7 @@ func showContactInfo(profile domain.Profile) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(*profile.PhoneNumberInternational) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 233, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 233, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -506,7 +506,7 @@ func showContactInfo(profile domain.Profile) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(profile.PhoneNumberE164) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 235, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 235, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -555,7 +555,7 @@ func contactInfoRequested() templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var22).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -568,7 +568,7 @@ func contactInfoRequested() templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs("Contact info requested") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 250, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 250, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -610,7 +610,7 @@ func requestContactInfo(page *controller.Page, profileID int) templ.Component { var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("request_contact_info", profileID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 256, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 256, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -623,7 +623,7 @@ func requestContactInfo(page *controller.Page, profileID int) templ.Component { var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs("Request contact info") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 267, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 267, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -665,7 +665,7 @@ func grantContactInfoRequest(page *controller.Page, profileID int) templ.Compone var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL("grant_contact_info", profileID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 274, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 274, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -678,7 +678,7 @@ func grantContactInfoRequest(page *controller.Page, profileID int) templ.Compone var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs("Grant access to contact info") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/profile.templ`, Line: 287, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/profile.templ`, Line: 287, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/pwa_install_templ.go b/app/goship/views/web/components/gen/pwa_install_templ.go similarity index 96% rename from app/goship/views/components/pwa_install_templ.go rename to app/goship/views/web/components/gen/pwa_install_templ.go index de4420e5..155a95b5 100644 --- a/app/goship/views/components/pwa_install_templ.go +++ b/app/goship/views/web/components/gen/pwa_install_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -44,7 +44,7 @@ func InstallButtonRedirectsToInstallPage(page *controller.Page, classes string) var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/pwa_install.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/pwa_install.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -57,7 +57,7 @@ func InstallButtonRedirectsToInstallPage(page *controller.Page, classes string) var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(page.ToURL(routenames.RouteNameInstallApp)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `app/goship/views/components/pwa_install.templ`, Line: 9, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `goship/views/web/components/pwa_install.templ`, Line: 9, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/app/goship/views/components/sidebar_templ.go b/app/goship/views/web/components/gen/sidebar_templ.go similarity index 86% rename from app/goship/views/components/sidebar_templ.go rename to app/goship/views/web/components/gen/sidebar_templ.go index c6d034d3..97da5ac0 100644 --- a/app/goship/views/components/sidebar_templ.go +++ b/app/goship/views/web/components/gen/sidebar_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -33,7 +33,7 @@ func Sidebar(page *controller.Page) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "