Compare commits

..
Author SHA1 Message Date
dev_agentandClaude Opus 5.5 9b66ba0ff5 Fix review findings F1-F3 for Docker context, health check and version
Independent review Approved — 0 high findings (#5)
F1: exclude generated internal/frontend/dist from the Docker build context.
F2: derive the health check host from MEALQUEST_HTTP_ADDR, mapping wildcard
hosts and localhost to 127.0.0.1 and keeping IPv6 brackets.
F3: compute the -dirty suffix ignoring the generated embed output, so a
rebuild of a clean checkout is not stamped dirty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:02:31 -04:00
dev_agentandClaude Opus 5.5 2e5724d150 Fix review findings F1 and F2 for the Dockerfile and .dockerignore
Independent review Not signed off — 0 high, 2 medium, 1 low (#5)
F1: derive the HEALTHCHECK port from MEALQUEST_HTTP_ADDR so an overridden
listen port is still probed at /health.
F2: make key/certificate exclusions recursive (**/*.pem, **/*.key) and add
**/*.p12, **/*.pfx, **/*.jks, **/*.keystore so nested local secrets never
enter the build context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:55:44 -04:00
dev_agentandClaude Opus 5.5 4dfc7baa2d Add Makefile dev/test/docker targets and multi-stage Dockerfile
Independent review Not signed off — 0 high, 2 medium (#5)
Makefile: dev runs the Go server (:8080) and Vite (:5173) together and
stops both on Ctrl-C; test runs the gofmt check, go vet, go test -race and
npm ci/typecheck/test; build stamps the version via -ldflags into a static
CGO_ENABLED=0 binary; docker builds the root Dockerfile. VERSION defaults
to git describe with a dev fallback.

Dockerfile: digest-pinned Node 22, Go 1.25 and Alpine 3.22 stages; the
runtime runs as uid 10001 with /data as a volume, EXPOSE 8080 and a
HEALTHCHECK on /health. .dockerignore keeps VCS, design, dependencies,
build output, databases and secrets out of the context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:50:16 -04:00
5 changed files with 0 additions and 467 deletions
-32
View File
@@ -1,32 +0,0 @@
# Runs on every push and pull request (no branch filter) so feature branches,
# their pull requests, and dev/main are all checked. Runs directly on the
# self-hosted act_runner; no container: key.
#
# The Makefile is the single build definition: CI runs `make test` and
# `make build` so it fails whenever either target would, and the binary
# carries the Makefile's -trimpath and version-stamped -ldflags (the
# `make build` log shows the -X main.version=... value).
name: CI
on: [push, pull_request]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: false
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: make test
run: make test
- name: make build
run: make build
-143
View File
@@ -1,143 +0,0 @@
# AGENTS.md — MealQuest agent guide
This is the authoritative guide for every agent (and human) working in this
repository. `CLAUDE.md` only imports this file. Where this guide and a general
habit disagree, this guide wins.
MealQuest is a single Go service with an embedded React PWA for one
household. The architecture of record is [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md);
product requirements are in [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md).
## House rules
1. **Fail fast.** Invalid configuration, input or state is an error reported
immediately and clearly — the service refuses to start on bad config.
2. **No silent fallbacks.** Never substitute a default, retry quietly, or
swallow an error to "keep going". If something is missing, say so.
3. **One way to do a thing.** No parallel code paths, alternative settings or
"just in case" options. Pick one approach and use it everywhere.
4. **No new third-party dependency without owner approval.** Use only the
approved list below; anything else needs the owner's explicit yes first.
5. **Never use `tea`.** The Gitea CLI on this host is authenticated as the
human owner. Agents act only through their own Gitea identities.
## Approved dependencies (from CTXD)
Backend:
- Go 1.25, built with `CGO_ENABLED=0`.
- `github.com/go-chi/chi/v5` — HTTP router.
- `modernc.org/sqlite` — CGO-free SQLite driver.
- `sqlc` — query code generation (dev tool only).
- `github.com/coreos/go-oidc/v3` — OIDC (Authentik).
- `golang.org/x/oauth2` and `golang.org/x/oauth2/google`.
- `github.com/SherClockHolmes/webpush-go` — Web Push.
- `golang.org/x/image` — photo processing.
- `github.com/oklog/ulid/v2` — IDs.
- Standard library `log/slog` and `net/http`.
- **Not allowed:** LLM vendor SDKs, Google API SDKs (call the HTTP APIs
directly), goose, golang-migrate.
Frontend:
- React 19, TypeScript, Vite.
- React Router 7, TanStack Query 5.
- `vite-plugin-pwa`.
- `lucide-react` icons at stroke width 2.75.
- CSS Modules over the design tokens in
[tokens.css](design/MealQuest%20UI%20Designs/design_handoff_mealquest/tokens.css).
**No Tailwind, no Framer Motion, no hard-coded hex colours.**
- Self-hosted fonts: Bagel Fat One and Figtree.
- Tests: Vitest, Testing Library, jsdom.
## Quality floor
Every change must pass all of these before review (CI in
[.gitea/workflows/ci.yml](.gitea/workflows/ci.yml) enforces them):
```sh
gofmt -l . # must print nothing
go vet ./...
go test -race ./...
cd web && npm run typecheck && npm test && npm run build
```
And the build entry points in the [Makefile](Makefile) and [Dockerfile](Dockerfile):
```sh
make test
make build
docker build .
```
Lint proves code parses, not that it works: where a change has runtime
behaviour, exercise it (hit the endpoint, render the page).
## Repository layout
| Path | Purpose |
| --- | --- |
| `cmd/server` | Entry point. Wiring only — no business logic. |
| `internal/config` | `MEALQUEST_*` environment config; secrets from `*_FILE`. |
| `internal/server` | HTTP server, routes, `/health`. |
| `internal/frontend` | `go:embed` of the built PWA with SPA fallback. |
| `internal/*` (planned) | `auth`, `db`, `store`, `secretbox`, `household`, `dinners`, `recipes`, `ai/llm`, `ai/harness`, `ai/skills`, `suggest`, `plan`, `shopping`, `ideas`, `gcal`, `notify`, `push`, `chat`, `jobs`, `scheduler`. |
| `migrations/` | SQL migrations (planned). |
| `web/` | React PWA source. |
| `deploy/` | Deployment files (planned). |
| `docs/` | Project documentation, including [ARCHITECTURE.md](docs/ARCHITECTURE.md). |
| `design/` | Requirements and UI handoff. **Read-only reference** — do not edit. |
| `Dockerfile` | Multi-stage image build, at the repository root. |
| `Makefile` | `dev`, `test`, `build`, `docker`. |
| `.gitea/workflows/ci.yml` | CI quality floor. |
Design references:
- [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md)
- [design/MealQuest UI Designs/design_handoff_mealquest/README.md](design/MealQuest%20UI%20Designs/design_handoff_mealquest/README.md)
- [design/MealQuest UI Designs/design_handoff_mealquest/tokens.css](design/MealQuest%20UI%20Designs/design_handoff_mealquest/tokens.css)
## Branches, pull requests and merges
- Integration branch is `dev`. `main` is the release branch.
- **Feature branches:** `feature/<feature-uuid>-<slug>`, branched from `dev`.
- **Task branches:** `task/<slug>_<task-uuid>`, branched from the feature branch.
- Task pull requests **squash-merge into the feature branch**.
- The feature pull request merges into `dev`.
- **Only `overseer` merges `dev` → `main`** (a release).
- Never push directly to `dev` or `main`.
- Authors never review, approve, merge or close their own work.
- Never store credentials in the repository; environment or ignored files only.
## Where context lives and where work is tracked
- **Context:** CTXD project `mealquest` — client guide at
<https://ctxd.cubecraftcreations.com>. Project documents: `CONTEXT.MD`
(architecture), `DECISIONS.MD` (decision log, e.g. D-021), `RUNBOOKS.MD`,
`PROMPTS.MD`, `GLOSSARY.MD`.
- **Work tracking:** Maestro at <https://maestro.cubecraftlabs.com>,
workspace **Maniacs-Mansion** → project **MealQuest**. Every change has a
task with a verifiable done-condition.
## Vocabulary (GLOSSARY)
Use these product terms in code names, UI copy, docs and commit messages. Do
not invent synonyms.
| Term | Meaning |
| --- | --- |
| **Regulars** | Dinners the household makes routinely. |
| **Side quest** | An AI-suggested new dinner to try. |
| **Quest log** | The to-try pool of accepted Side quests, scheduled later. |
| **Try it / Pass** | Accept (swipe right) / decline (swipe left) a suggestion. |
| **Rotation check** / **"Keep it in rotation?"** | The prompt after a Quest-log dinner is tried, asking whether it becomes a Regular. Promotion is always the household's choice, never automatic. |
| **Supply run** | The current week's shopping list, grouped by dinner. |
| **Supply archive** | The blog-style history of past shopping lists (by week, month, year). |
| **Draft board** | The mobile weekly plan layout the AI drafts for the household to adjust. |
| **Rebuild week** | Ask the AI to redraft the week's plan. |
| **Push to Calendar** | Push the plan one-way to Google Calendar. |
| **Quick dinner** | Cooked in a crock pot **or** 30 minutes or less from prep to table. |
| **Cook from what I have** | Ideas from a list of ingredients on hand. |
| **Kitchen reading mode** | Tablet recipe view: large text, ingredient checkboxes, step-by-step. |
| **"Why this?"** | The reason shown for a suggestion, referencing the household's Regulars. |
| **Our kitchen / Shared household** | The single shared household identity; no per-person profiles. |
-1
View File
@@ -1 +0,0 @@
@AGENTS.md
-56
View File
@@ -1,56 +0,0 @@
# MealQuest
MealQuest is a mobile-first Progressive Web App for weekly dinner planning for
one household. The household lists the dinners it makes regularly, an AI
assistant suggests new dinners to try (swipe "Try it" or "Pass"), and the app
builds a weekly plan, pushes it to Google Calendar, and generates a shopping
list grouped by dinner, kept as a browsable weekly history. It ships as a
single Go service with the React PWA embedded in the binary.
> Status: this repository is the Feature 01 scaffold (Go service with
> `/health`, embedded PWA skeleton, Makefile, Dockerfile, CI). The planned
> application is described in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## Quick start
Requirements: Go 1.25 and Node.js `^22.22.2 || ^24.15.0 || >=26.0.0` — that
is, 22.22.2 or later on the 22 line, 24.15.0 or later on the 24 line, or 26.0.0
or later. Node.js 23, 24.0–24.14 and 25 are not supported: this is the
`engines` range of jsdom in the committed lockfile, the strictest of all the
web dependencies.
```sh
(cd web && npm ci)
mkdir -p ~/.mealquest-dev # outside the repository: it holds a secret
chmod 700 ~/.mealquest-dev
head -c 32 /dev/urandom | base64 > ~/.mealquest-dev/secret_key
chmod 600 ~/.mealquest-dev/secret_key
export MEALQUEST_HTTP_ADDR=:8080
export MEALQUEST_PUBLIC_URL=http://localhost:5173
export MEALQUEST_DATA_DIR="$HOME/.mealquest-dev"
export MEALQUEST_SHUTDOWN_TIMEOUT=10s
export MEALQUEST_SECRET_KEY_FILE="$HOME/.mealquest-dev/secret_key"
make dev
```
`make dev` runs the Go server on `:8080` and the Vite dev server on `:5173`
(which proxies `/api`, `/auth` and `/media` to `:8080`). Open
http://localhost:5173. Configuration is environment-only and fails fast:
every variable above is required, and secrets are read only from files named
by `*_FILE` variables.
## Common commands
| Command | What it does |
| -------------- | --------------------------------------------------------------- |
| `make dev` | Go server plus Vite dev server. |
| `make test` | `gofmt` check, `go vet`, `go test -race`, web typecheck and tests. |
| `make build` | Build the PWA, embed it, build the static binary `bin/mealquest`. |
| `make docker` | Build the container image from the root `Dockerfile`. |
## Where to read next
- [AGENTS.md](AGENTS.md) — house rules, approved dependencies, conventions.
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — architecture of record.
- [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md) — product requirements.
- [design/MealQuest UI Designs/design_handoff_mealquest/README.md](design/MealQuest%20UI%20Designs/design_handoff_mealquest/README.md) — UI handoff.
-235
View File
@@ -1,235 +0,0 @@
# MealQuest architecture
This mirrors the architecture section of `CONTEXT.MD` in the CTXD project
`mealquest`, which is the source of record; if the two disagree, CTXD wins and
this file should be updated. It describes the **planned** application. The
repository today is the Feature 01 scaffold: `cmd/server`, `internal/config`,
`internal/server` (`/health`), `internal/frontend` (embedded PWA with SPA
fallback), the `web/` skeleton, `Makefile`, `Dockerfile` and CI.
Decision numbers (D-NNN) refer to `DECISIONS.MD` in CTXD.
## Topology
```
PWA (phone / tablet / desktop)
│ HTTPS
▼
Traefik v3.6 — 10.60.1.33
file route: mealquest-ccl.yml
│
▼
docker compose project "mealquest" — 10.60.1.14
├── mealquest Go service + embedded PWA, host 8090 → container 8080
├── searxng sidecar for recipe search; internal compose network
│ only, no published port
└── Litestream SQLite replication → Synology NAS 10.60.1.50
```
One Go service serves the API, the auth flow, media and the PWA. Its SQLite
database is replicated by Litestream to the NAS.
## Repository layout
| Path | Purpose |
| --- | --- |
| `cmd/server` | Entry point; wiring only. |
| `internal/config` | Env-only `MEALQUEST_*` config; secrets from `*_FILE`. |
| `internal/server` | HTTP server, router, middleware. |
| `internal/frontend` | Embedded PWA, SPA fallback. |
| `internal/auth` | Authentik OIDC, sessions, CSRF. |
| `internal/db`, `internal/store` | SQLite connections, sqlc queries. |
| `internal/secretbox` | AES-256-GCM encryption for `_ct` columns. |
| `internal/household`, `internal/dinners`, `internal/recipes` | Domain. |
| `internal/ai/llm`, `internal/ai/harness`, `internal/ai/skills` | AI provider adapters, skill harness, skills. |
| `internal/suggest`, `internal/plan`, `internal/shopping`, `internal/ideas` | Feature services. |
| `internal/gcal`, `internal/notify`, `internal/push`, `internal/chat` | Integrations. |
| `internal/jobs`, `internal/scheduler` | Background jobs and schedules. |
| `migrations/` | SQL migrations (no goose or golang-migrate). |
| `web/` | React PWA. |
| `deploy/` | Deployment files. |
| `docs/` | Documentation. |
| `design/` | Requirements and UI handoff (read-only reference). |
## Backend stack and conventions
- Go 1.25, `CGO_ENABLED=0`, a single static binary.
- Router: `go-chi/chi/v5`. Logging: `log/slog` JSON.
- SQLite via `modernc.org/sqlite`, with `journal_mode=WAL`,
`foreign_keys=ON` and `busy_timeout`. **One writer connection plus a read
pool.** Queries generated by `sqlc`.
- IDs are ULIDs (`oklog/ulid/v2`).
- Timestamps are stored in UTC; local calendar dates are `YYYY-MM-DD` strings
in the household timezone.
- Configuration is environment-only (`MEALQUEST_*`). Secrets come only from
files named by `*_FILE` variables. Missing or invalid config stops startup.
- Error responses always have the body
`{"error":{"code":"…","message":"…","retryable":false}}`.
## Authentication
- Authentik OIDC; the backend is a **confidential client**
(`coreos/go-oidc/v3`, `golang.org/x/oauth2`).
- Session cookie `__Host-mealquest_session` (HttpOnly, Secure,
SameSite=Lax). Sessions expire after 30 days idle or 180 days absolute.
- Non-GET requests require the CSRF header `X-MealQuest-CSRF`.
- Access requires membership of the Authentik group `mealquest-admin`.
There is a **single role**: one shared household, no per-person profiles.
## Data model
Columns ending in `_ct` hold AES-256-GCM ciphertext (via `secretbox`).
| Table | Holds |
| --- | --- |
| `household_settings` | One row: timezone, dinner_time (17:30), reminder_time (16:30), review_time (09:00), plan_draft day/time (Fri 09:00), suggestions day/time (Sun 08:00), chat_autosend day/time (Sat 08:00), onboarding_completed_at, updates_seen_at. |
| `recipes` | A real recipe page: source_url (unique), site_name, title, total/prep/cook minutes, yield, raw_jsonld, fetched_at. |
| `recipe_ingredients` | recipe_id, position, raw_text, name, qty, unit, note. |
| `recipe_steps` | recipe_id, position, text. |
| `photos` | source_url, local_path, width, height, sha256, fetched_at. |
| `dinners` | The meal record and its lifecycle: name, normalized_name, status (`suggested` / `quest_log` / `tried` / `regular` / `archived`), origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by. |
| `suggestion_batches` | trigger, status, ai_run_id. |
| `suggestions` | dinner_id, batch_id, reason, cited_regular_ids (JSON), status, decided_at. |
| `week_plans` | week_start (unique), status (`draft` / `synced` / `changed`), ai_note, ai_run_id, calendar_synced_at. |
| `plan_days` | plan_id, date (unique per plan), dinner_id, is_new, calendar_event_id. |
| `rotation_reviews` | dinner_id + plan_date (unique), status, decision (`keep` / `drop` / `not_made`), decided_at, updated_by. |
| `shopping_lists` | week_start (unique), plan_id, title, status (`current` / `archived`), sent_to_chat_at, frozen_at. |
| `shopping_groups` / `shopping_items` | One group per dinner/day; item name, qty, needed (bool), position. |
| `ai_settings` | One row: provider, base_url, model, temperature, system_prompt, api_key_ct, api_key_last4, key_verified_at. |
| `recipe_search_settings` | One row: provider (`searxng` / `brave`), base_url, api_key_ct. |
| `integrations_google` | One row: refresh_token_ct, calendar_id, calendar_name, connected_at, last_sync_error. |
| `chat_settings` | One row: webhook_url_ct, space_label, last_test_at, last_test_status. |
| `notification_prefs` | event (`plan_ready` / `list_ready` / `day_of_reminder` / `new_suggestions` / `rotation_review`), push, chat. |
| `push_subscriptions` | endpoint (unique), p256dh, auth, user_agent, failure_count, last_success_at. |
| `notification_outbox` | event_key + channel (unique), payload, status, attempts, next_attempt_at, last_error. |
| `jobs` | kind, status, payload, result (progressive), error, timestamps. |
| `schedule_runs` | schedule, period_key (unique). |
| `ai_runs` | skill, skill_version, provider, model, temperature, status, latency_ms, tokens_in/out, error, redacted request/response (30-day retention). |
| `sessions` | id_hash, subject, email, name, groups, csrf_token, created/last_seen/expires. |
`recipes` holds a fetched recipe page; `dinners` holds the household's meal
record and its lifecycle, optionally linked to a recipe and a photo.
## Dinner lifecycle
```
suggested (deck) ──Try it──▶ quest_log ──planned date passes──▶ tried
│ ▲ │
│ └──────── explicit decision ─────┤
│ ├──▶ regular
│ └──▶ archived
└──Pass──▶ archived (name kept for dedupe)
```
From `tried`, an explicit decision moves the dinner to `regular`, to
`archived`, or back to `quest_log`. Promotion to `regular` is **never
automatic** (D-021).
## API surface
- JSON API under `/api/v1`, using the error body above for every failure.
It requires the session cookie and, for non-GET requests, the
`X-MealQuest-CSRF` header.
- `/auth/*` — the OIDC flow.
- `/media/*` — cached photos.
- `/health` — liveness.
- Everything else serves the embedded PWA with SPA fallback.
## AI layer
- **Provider interface** (`ai/llm`) with adapters for OpenAI-compatible,
Anthropic and Ollama endpoints, called over plain HTTP — no vendor SDKs.
- **Skill harness** (`ai/harness`): prompt layering, validation of the
structured output, and **one repair retry**; if the repair also fails the
run fails — no silent fallback. Every call is recorded in `ai_runs`.
## Recipe grounding
- Dinners are grounded in real recipe pages found through recipe search
(`searxng` or `brave`, per `recipe_search_settings`).
- Fetching is **SSRF-safe**.
- Recipe data is parsed from schema.org `Recipe` JSON-LD.
- Photos are cached locally (`photos`) and processed with
`golang.org/x/image`.
## Scheduler
Times are in the household timezone. The times of `day_of_reminder`
(`reminder_time`) and `rotation_check` (`review_time`), and the day and time
of `suggestions_weekly`, `plan_draft` and `shopping_chat_autosend`, are
household settings in `household_settings` (`dinner_time` is also stored
there); the table shows their defaults. `week_rollover` and the `ai_runs`
purge are fixed and not configurable.
| Schedule | When | Does |
| --- | --- | --- |
| `suggestions_weekly` | Sunday 08:00 | New batch of Side quests. |
| `plan_draft` | Friday 09:00 | AI drafts next week's plan. |
| `shopping_chat_autosend` | Saturday 08:00 | Sends the Supply run to Google Chat. |
| `day_of_reminder` | Daily 16:30 | Tonight's dinner reminder. |
| `rotation_check` | Daily 09:00 | Asks "Keep it in rotation?" for tried dinners. |
| `week_rollover` | Monday 00:05 (fixed) | Freezes last week's Supply run into the Supply archive. |
| `ai_runs` purge | Daily (fixed) | Deletes `ai_runs` rows past 30-day retention. |
Runs are recorded in `schedule_runs` and executed through `jobs`.
## Notifications
- Events: `plan_ready`, `list_ready`, `day_of_reminder`, `new_suggestions`,
`rotation_review`; each can go to Web Push and/or Google Chat per
`notification_prefs`.
- Delivery goes through `notification_outbox`.
- Web Push uses VAPID (`webpush-go`). Google Chat uses the stored incoming
webhook.
## Google Calendar
- One-way push: the app is the source of truth; edits in Google Calendar do
not come back.
- Connected once with Google OAuth in Settings; one event per planned
dinner.
- **Deterministic event ids**; each plan day records its
`calendar_event_id`.
## Frontend
- React 19, TypeScript, Vite, React Router 7, TanStack Query 5,
`vite-plugin-pwa`, `lucide-react` (stroke 2.75).
- CSS Modules over `tokens.css`; no Tailwind, no Framer Motion, no
hard-coded hex. Self-hosted Bagel Fat One and Figtree.
- Tests: Vitest, Testing Library, jsdom.
- Offline cache for the current plan, shopping list and recipes.
| Route | Screen |
| --- | --- |
| `/` | Home |
| `/onboarding` | Add regular dinners |
| `/plan` | Weekly plan (Draft board) |
| `/plan/shopping` | Shopping list (Supply run) |
| `/plan/shopping/archive` | Supply archive |
| `/discover` | Swipe suggestions (Side quests) |
| `/quick` | Quick dinners |
| `/cook-from-pantry` | Cook from what I have |
| `/dinner/:id` | Recipe / dinner detail (Kitchen reading mode on tablet) |
| `/settings` | Settings home |
| `/settings/household` | Household settings |
| `/settings/regulars` | Regular dinners |
| `/settings/calendar` | Google Calendar connection |
| `/settings/notifications` | Notifications and Google Chat |
| `/settings/ai` | Admin · AI assistant |
`/settings/household`, `/settings/regulars` and `/settings/calendar` come from
CTXD; the other routes are defined in the UI handoff.
## Boundaries and gotchas
- **Secrets never reach the client** — API keys, OAuth tokens and the Chat
webhook stay server-side, encrypted at rest.
- **The app is the source of truth for the plan**; Google Calendar is a
one-way mirror.
- **Never auto-promote to Regular** (D-021).
- **Quick** means cooked in a crock pot **or** ≤ 30 minutes prep to table.
- **Weeks run Monday–Sunday** in the household timezone.
- **No silent fallbacks** — fail fast and report.
- **iOS Web Push requires the PWA to be installed to the Home Screen**; show
an install hint.