Show extra costs, and open screenshots full screen (store detail) - #424
Conversation
Ports both recent app-page changes from ../cloud onto the box UI, so the two surfaces that show the same catalog show it the same way. external_costs has reached the box API since catalog-wire-external-costs and nothing rendered it: ten catalog apps declare one, listmonk needs a paid mail provider before it sends anything, and a person learned about that bill after installing. A pricing panel above Information now shows a Price row plus each cost as a <details> disclosure, required ones open (the one you have to read to decide) and optional ones collapsed, with the manifest's rate estimate and prose. The "you pay the provider, not malmo" note is a CSS-only tooltip (group-hover / group-focus-within), so it opens for a pointer, a keyboard and a tap with no script. Price stays a constant in the view, not a wire field. Every app is free today and what malmo charges is authored in the curation source, so this is the one line to change when it does reach the wire, matching cloud's appPrice. The screenshot strip was inert. Each shot now opens fit to the viewport, whole rather than a zoom of the thumbnail in place, with arrow keys, prev/next and a live "2 of 3" counter that wraps both ways. It is a native <dialog>, not the fixed-overlay idiom AppMenuDialog hand-writes: the browser supplies the top layer, the backdrop, Esc, the focus trap and focus restore, leaving an index and a src swap. showModal does not stop every browser's wheel scroll, so the document is pinned while it is open and released on close, once, for every dismissal path. Cloud's progressive-enhancement half is deliberately not ported: that page is server-rendered HTML, this one is a Vue view that needs script to exist at all, so the thumbnails are buttons rather than links. Hover lifts with a shadow instead of darkening the hairline border, for the same reason cloud changed it: a pale line vanishes into a light screenshot and frames a dark one. No brain change, no wire change, no new dependency. Claude-Session: https://claude.ai/code/session_012y33wKNxu7iUd1wLa95TXg
Confidence Score: 4/5The PR is not yet safe to merge because the screenshot modal still permits the signed-in page’s actual scroll container to move behind it. The existing scroll-lock finding remains outstanding: AppDetailView hides overflow on the document root, while AppShell’s independently scrollable main element continues to own page scrolling. Files Needing Attention: web-ui/src/views/AppDetailView.vue
|
| Filename | Overview |
|---|---|
| web-ui/src/views/AppDetailView.vue | Adds pricing and screenshot-viewer behavior; the stale-index fix is effective, but the previously reported scroll-lock target remains incorrect. |
| docs/progress/store-detail-costs-and-screenshots.md | Documents the new pricing and screenshot features, implementation choices, validation, and known gaps. |
| docs/progress/README.md | Adds the completed work to the chronological progress index. |
Reviews (2): Last reviewed commit: "Lift the screenshot viewer's scroll pin ..." | Re-trigger Greptile
| restoreOverflow = document.documentElement.style.overflow; | ||
| document.documentElement.style.overflow = "hidden"; |
There was a problem hiding this comment.
When a screenshot is open, openShot hides overflow on documentElement, but the signed-in console scrolls through AppShell's independent overflow-y-auto <main>. The store page therefore continues moving behind the modal and can return the user to a different position when the viewer closes.
Knowledge Base Used: Web console experience
Review of the port found a real bug it introduced. The document scroll was pinned on open and released on the dialog's close event only, but a dialog can stop being on screen without closing: taking an open <dialog> out of the DOM fires no close event. The page this was ported from is one server-rendered document and can only be left by a full load; this is a router route, so the Back button unmounts the component mid-viewer. The pin then outlived the dialog and every other page in the dashboard was unscrollable until a reload. The pin now has its own flag and three release sites: the dialog's close, onUnmounted, and a watcher on the shot list — a background refetch that returns the same app with no screenshots removes the open dialog through the v-if, which is the same hole again. That watcher also clamps the index, so a shorter list cannot render a broken image under a "6 of 3" counter. Claude-Session: https://claude.ai/code/session_012y33wKNxu7iUd1wLa95TXg
What & why
The store detail page (
/store/:id) now shows what a third party charges for an app, and opens its screenshots full screen. Both are ported from the marketing store's app page in../cloud(internal/web/templates/pages/app.html,internal/web/static/app-gallery.js), so the two surfaces that show the same catalog show it the same way.external_costshas reached the box API sincecatalog-wire-external-costs, and nothing rendered it — ten catalog apps declare one, listmonk needs a paid mail provider before it sends anything, and a person learned about that bill after installing. The screenshot strip was inert: a click did nothing, so the only view of an app was a 224px crop.Spec(s) touched
None.
APP_MANIFEST.mdalready describesexternal_costsandAPP_STORE.mdalready describes the catalog fields this renders. No wire change, no brain change, no new dependency — display side only.What was tested
make check-webgreen (typecheck + production build).group-open:rotate-90,backdrop:bg-black/90,pl-4.5all emit rules.external_costs; listmonk's is required, postiz's optional with 3 screenshots — so both branches of the pricing panel and the viewer's prev/next have real content.make dev; anything it turns up lands on this branch before merge.Known gaps & deviations
web-uihas no test runner, so this is covered by the type checker and the build only — the standing gap for every view in this directory.<a>links to the full-size asset, so a blocked script still opens the image. That page is server-rendered HTML; this one is a Vue view that needs script to exist at all, so the thumbnails are buttons."Free"), not a wire field. Every app is free today and what malmo charges is authored in the curation source, not published. Mirrors cloud'sappPrice; one line to change when a price does reach the wire.Closes #Nline — it closes the "No UI yet" gap recorded indocs/progress/catalog-wire-external-costs.md.Platform gaps
None — not a catalog PR.
Definition of done
make dev) — being verified now, not yet confirmed by the author.web-uihas no test runner);make check-webgreen. No Go change, somake checkis untouched by this diff.*_test.goin the diff.docs/progress/store-detail-costs-and-screenshots.md); progress index updated.external_costsalready ships on it.DECISIONS.mdentry.CLAUDE.md. Nolog/slogsurface — frontend only.dev, PR intodev.