Skip to content

feat(dashboard): store admin SPA with the product catalog flow - #26

Merged
Rl0007 merged 197 commits into
mainfrom
feat/admin-dashboard
Sep 8, 2026
Merged

feat(dashboard): store admin SPA with the product catalog flow#26
Rl0007 merged 197 commits into
mainfrom
feat/admin-dashboard

Conversation

@Rl0007

@Rl0007 Rl0007 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Vue Based Backend, use /commera to access it.

Demo

ci-green-demo.mp4

In the current app you had to jump at least 8 DocTypes just to create an item and add prices to it. Now everything is controlled from a single Vue Frontend.

It has lot of features:

  1. Create Products (Bulk as well)
  2. Dashboards
  3. Track Orders
  4. Analytics (Storefront, inventory etc.)
  5. And a lot more.

I will write a detailed docs for it.

Apart from this we are also introducing Storefronts like Shopify, now you can update the themes of your customer facing websites with few clicks. You can also create your own Storefronts. I will also add these in docs, maybe will add a tutorial for it.

new-recording-small.mp4

Rl0007 and others added 30 commits August 19, 2026 17:02
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… layout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tions table

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… inventory table

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Homepage-complete second theme for the shop_themes engine, ported from the
Pixio bootstrap template. Splits the shared document shell out of the default
theme into shop_base_theme/components/base.html so a theme owns only its chrome.

Vendored CSS, fonts, icons and demo photography come from the public Pixio demo
and are NOT licensed - they must be replaced from the purchased package and the
demo imagery stripped before this ships anywhere public.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The homepage sections all read Pixio Theme Settings, which until now was only
populated by throwaway console scripts - so any fresh site rendered every
section empty. Idempotent: tables fill only while empty, scalars only while
falsy, so a re-run never clobbers merchant-curated content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Words come from the enabled category tree rather than a settings field, so the
strip self-updates instead of drifting out of sync with the menu.

Two tracks instead of the reference's one: upstream pins its single ul to the
wrapper width while the content is 2.7x wider, so its -100% keyframe scrolls one
viewport and snaps mid-word. Duplicating the track makes the loop seamless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(core): read the customer group from Selling Settings

_create_party_for_user read a customer_group field off Lifestyle Settings that
has never existed, so party creation threw and every checkout failed at the
quotation step - on the un-themed storefront too, not just Pixio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(core): resolve a leaf customer group for party creation

get_root_of("Customer Group") returns the is_group=1 root, which Customer
rejects, so an unset Selling Setting still broke checkout. Falls through to
frappe's default then to any leaf group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ettings tabs

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ERPNext moved its transaction mappers into sibling `mapper` modules, and both layouts are
live across the versions this app runs against. The pinned import raised at module import
time, which took `ls_shop.migrate` down with it and skipped the whole `after_migrate` hook -
so route seeding, the search index and the payment modes never ran on an older bench.

Mirrors the resolution already used in `api/admin/orders.py`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
XS-XXL are labels, not measurements, so `numeric_values` made ERPNext demand a from/to
range on Item Attribute and throw "From Range has to be less than To Range". It is the
first step of the installer, so the whole demo catalog failed before anything was created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`configure_lifestyle_settings` never set the mandatory company, and steps 5 and 6 assumed the
Website Item doctype exists. Website Item ships with webshop, which this app no longer
depends on, so on a bench without it the installer threw and rolled the whole catalog back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Builds the menu the storefront header renders, without opening Desk. The tree is drag and
drop at every level: dropping inside another entry re-parents it, dropping between siblings
reorders them, and sortable's own move hook refuses a drop that would bury a branch past the
depth the server allows, so the gesture never animates into an error.

An entry can link to several item groups at once, which is the case Desk's Link field could
not express - products from every listed group are shown together under the one entry.
Building the whole menu from an existing item group tree is one action.

The preview frames the real storefront rather than mocking a menu, so what is checked is the
theme's own header. Every mutation returns the whole tree, so the editor adopts the server's
answer instead of patching a local copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rl0007 and others added 13 commits September 7, 2026 04:23
The test sampled five arbitrary Sales Orders and compared the cost of reading
the first against the cost of reading all five. An order with no paperwork skips
the delivery-note and packing-slip reads entirely, so as soon as the newest order
on a site had no delivery note the two numbers differed for a reason that has
nothing to do with scaling, and the test failed on data rather than on code.

Measured directly, the reader costs four queries for one order and four for
five. Sampling only orders that carry a delivery note keeps the assertion about
what it claims to be about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
An audit found 22 controls and screens that look finished and are not. They do
not crash, so clicking through never caught them. Each one is now wired to a
real endpoint, or gone.

Wired: delete a product (refusing, with a reason, anything that has ever been
ordered), copy and preview the storefront link, receive stock, set the low-stock
level, move products into a collection from both the bulk bar and the product
menu, print a delivery note and an invoice, publish the products under a menu
entry, view the storefront, log out, and refund an order. Settings now reads and
writes the store's own records instead of rendering an invented company, an
invented warehouse and three invented members of staff; its long tail of fields
comes from the doctype's own meta, so adding a field surfaces it.

The import wizard's Images step was the worst of them: a setTimeout progress bar
ending in "356 of 372 images matched by file name", hardcoded, over an import
that really did create the products. Photos are now really uploaded, really
matched to a product and colour by file name, and really attached - and every
number on the screen is counted from the files chosen.

Removed rather than dressed up: product types (a routed screen with no backend
at all, leaking mock rows onto the real product form), a simulated zip import, a
storefront listing form that saved nowhere, the URL-column photo mode (making it
real means fetching arbitrary URLs server-side), and the grey furniture whose
only purpose was to name a feature that does not exist.

Two screens turned out to have a wired implementation sitting unused beside a
mock: the theme editor and the refund dialog were both complete and imported by
nothing, while the route and the menu pointed at fixtures. Both now reach the
real one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
Saving the cart Quotation resolves the party's receivable account, and ERPNext's
account_perm_check calls frappe.has_permission("Account") directly, so no
ignore_permissions reaches it and no storefront role has Account read. Every
write to the cart in the shopper's own session was refused: Place Order, the
checkout page's shipping rule, the address step, the coupon, the delivery
charges. Nobody could buy anything.

system_user_session() already existed three functions away, its docstring already
named this exact ERPNext behaviour, and place_order and place_cod_order already
used it - the cart saves did not. They do now, through one helper, with only the
save inside the elevated window: the caller has already proved the cart belongs
to the session user, because _get_cart_quotation scopes on contact_email, and
every ownership and validation check still runs as the shopper.

The stock check existed too, and only the cart's own JavaScript ever called it,
so a tampered client could order 9999 of an item with 15 in stock and get a
quotation for it. It is enforced on the server now, through the same function
rather than a second implementation.

An item with no row in the sale price list showed a real price on the product
page and nothing in the cart, because the cart alone had no fallback. It falls
back the same way the product page does. Note this fixes what the cart shows,
not the rate ERPNext books: the quotation still sells from the sale price list
alone, which is a decision about how the two configured lists relate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
Storefront Pages had no way in at all: both "Add page" buttons passed `to`,
which is not a frappe-ui Button prop, so it landed in $attrs as an inert DOM
attribute. With no pages on the site there was no row to click either, and a
working editor sat behind a button that did nothing. It is the only misuse of
that prop in the tree.

The theme editor's preview pane was a 500. theme_editor_preview imported two
names that had never existed, so the module failed on import - invisible until
the Theme route stopped pointing at a mock and started rendering the real editor.
render_page_preview now renders the live theme's home page the way a real request
does, and blanks the tracking blocks so an editor pane emits no analytics.

Saving a price of zero reported "Price updated" and wrote nothing: the pricing
helper treats a non-positive rate as "leave this list alone", which the create
flow depends on, so the refusal belongs at the endpoint. It says a price cannot
be removed once set, because there is nowhere in the app that can remove one.

The Overview's attention tiles counted the preview list, which the API caps at
five, so they read five however many there were. Both totals come from the
server now, out of the same pass that builds the preview.

Also: bulk actions no longer sit enabled over an empty selection; three menu rows
that promised editing now say what they really do - a read-only list of
addresses, a read-only list of options, and the unfiltered orders list; Settings
no longer states that no company is set while it is still asking; an unknown
/commera path gets a real not-found screen instead of an empty shell; the variant
dialog stops rendering the literal word undefined; and a Margin column that could
never hold a value is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
Orders offered Export and Create order, Customers offered Export and Add
customer, and all four only ever toasted that they were not supported. Create
order and Add customer contradict a decision already made: a shopper places
their own order and a customer record is created the moment they check out,
which is why validate_document_access compares owner to the session user. The
two exports have no endpoint, the same reason the product CSV export went.

The import wizard keeps its six sources. Only the spreadsheet works, but the
other five stay named and badged: a merchant arriving from Shopify should see
that we know about it and that it is not their way in yet. That is what the
step's own comment argued, and it is a different case from a button that looks
like every other working button in a header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
Publishing one option and receiving stock against it both existed, on the
variant's own page - product, swatch, dialog, "Open full page". Three clicks from
the matrix that shows every option, which is where a merchant is standing when
they want either.

Both are row actions on that matrix now, and the matrix says what the storefront
thinks: Live, Hidden, or the reason it cannot go live yet. That reason is the
server's own blocker list, the same text set_variant_published refuses with, so
an option missing a photo says so before the click rather than after it.

Receiving is the additive receipt the Stock screen and the product menu already
use - one quantity onto every size of the option - because ls_shop has no way to
set on-hand to an exact number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdA6pnQb2yRMg8RFZdV1pH
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0127esXD5REhi7otqsBLYVaW
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0127esXD5REhi7otqsBLYVaW
…mark

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
The variant matrix edited the compare-at while the storefront charges the sale
rate, so a price rise never reached the shopper. Settings autosave shared one
request across every field, so overlapping commits resolved against each other.
A failed import left `finished` set, so every retry repainted a success that
never ran. The shell reached the server on the v1 path the api wrappers exist
to avoid.

The login selector in the e2e harness went stale against Frappe's login page;
the suite could not reach the app at all without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
The global file's frontend doctrine is written for the storefront's Jinja and
Alpine pages and says no SPA frameworks, so every reviewer of this directory
was applying the wrong law to it. Nine numbered sections a review can cite:
the one door to the server, who toasts a failure, how a settings panel saves
itself, where a file goes, what to reuse before building, tokens, comments,
style, and how to verify.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
…rules

The appearance cards marked the chosen one with three more pixels of border, so
nothing read as selected. They are ours now rather than frappe-ui's deprecated
switcher: a tick on the chosen card, native radios for the keyboard, and the two
CSS overrides that reached into the old component's internals are gone.

A rule sat between a group's heading and the first field under it, fencing the
heading off from what it heads. The delete action no longer announces itself
under a Danger heading, and the Advanced warning has room above it.

The comments that cited the wiring map now carry the fact itself, since that
file goes in the next commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Planning and audit notes from building the dashboard, all of them spent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
@Rl0007
Rl0007 marked this pull request as ready for review September 8, 2026 05:50
Rl0007 and others added 15 commits September 8, 2026 12:01
…hips

frappe-ui's preset defines gray, blue, green, red, orange, yellow, teal, violet,
cyan, amber, pink and purple — and no neutral. Every bg-neutral-* in the previews
emitted no rule at all, so the desktop backdrop, the dark window and all three
content lines in every frame painted transparent: in the light theme the three
cards were identical white rectangles, and in the dark theme the dark card was
empty but for its traffic lights.

The cards also stack below sm, where three frames across a phone read as portrait
slivers rather than windows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Four of the five failing jobs were failing on their own setup rather than on
the code.

Server never installed bwh_payments or bwh_shipping, which the app imports 34
times; bwh_shipping was not in required_apps either.

Dashboard Typecheck cached a yarn.lock that is not tracked, installed with yarn
where the repo tracks package-lock.json, and ran a typecheck script that does
not exist against a codebase with no TypeScript. It builds the dashboard now,
which is the check that was actually meant, and the lockfile it needs was out
of sync with package.json so npm ci could not have run either.

Semantic Commits had no config, so it rejected this repo's own `merge:` subject
under commitlint's defaults.

Pre-Commit was linting code we do not author: biome retabbed vendored Alpine and
every Frappe-written .json — doctypes and record exports that bench rewrites at
one-space indent — and the comment cap read third-party licence banners. The
findings that were ours are fixed: a forEach, an assignment inside an
expression, and the formatting biome could apply itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Dead since the output moved to public/commera, and a wide `git add` swept all 63
files of it into a commit once already.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Whole repo rather than the lines this PR happens to touch: semgrep ci picks by
baseline, and the baseline moves with every push, so a line-scoped fix only
defers the failure. 119 findings to zero.

Two were not lint. enqueue_publish_all_variants inserted its log row in the
request transaction and the job read that row immediately, so a fast worker
raised DoesNotExistError before the request had committed - it takes
enqueue_after_commit now. And api/translation.py served the entire Translation
table to guests with no rate limit; nothing imported it, so it is gone.

The suppressions that were already here did nothing: semgrep reads
`# nosemgrep: <text>` as naming a rule, so a prose reason suppresses a rule by
that name, which is to say nothing at all. Every one now names the rule id.

The rest is 84 missing argument type hints, each read off the body rather than
guessed, and guest endpoints and manual commits kept where they are correct with
the reason recorded against them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
The concurrency group keyed on github.event.number, which is empty on push - so
every push to develop and main shared one group and cancelled the one before it.
The linters pinned Python 3.13 while pyproject requires >=3.14, so semgrep and
ruff ran on a version the app does not support. And a Server job that dies
before the tests print nothing at all; it dumps bench_start.log and the site
logs now, the way every upstream Frappe repo does.

Dependabot watches the dashboard lockfile and the actions, weekly, minor and
patch grouped. The audit gates on critical rather than high because the tree
already carries a high - vite <=6.4.2 through esbuild - that only a major bump
clears; a check that lands red teaches people to ignore it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
The hook added in 9905d83 has never passed: 141 comments and docstrings ran
past it, so the Pre-Commit job has been red since the day the rule landed.

Each one keeps the fact and loses the exposition, per the rule's own test -
a quirk, an ordering requirement or a defect reference stays; why it is built
this way goes in the commit message. Nothing was deleted wholesale, because
every block carried something worth keeping.

Three facts that only made sense against one specific line moved to sit on that
line instead: the mask-size stretch on the promo band, the breakpoint ladder
that keeps .swiper-product apart from the rails, and the shared Item Attribute
the onboarding test appends to.

Comments only. Every touched Python file parses to the same tree with docstrings
blanked, and every touched stylesheet is byte-identical once comments are
stripped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Pre-Commit was not reporting lint errors: ruff-format and biome-check are
fixing hooks, and they were rewriting six Python files and the commitlint
config on every run, which pre-commit reports as a failure. The drift predates
this branch - the pinned ruff 0.8.1 wraps six long lines a newer local ruff
leaves alone.

Formatting only: every file parses to an identical tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Two bugs stacked, and the first hid the second.

get_refund_status looked for Payment Entry Reference rows against the Sales
Order, but the capture chain books the payment on the Sales Invoice
(api/payments.py:213 raises it with get_payment_entry("Sales Invoice", ...)),
so no paid order ever matched and every one reported can_refund: False.
get_order_payments walks the invoice hop, and keeps the direct Sales Order
shape too so a Desk advance still resolves.

Behind that, the Payment Entry could never insert: validate_mandatory() runs
before set_amounts(), so set_received_amount() never derives received_amount
and ERPNext refuses the entry. It is set explicitly now, guarded by a currency
check rather than an assumption - a payment that crossed currencies is refused
and sent to the accounts desk instead of paid out at an invented rate.

This also unblocks the whole suite: the missing get_order_payments broke Frappe's
test discovery, so `run-tests --app ls_shop` collected nothing at all. 505 tests
run now where none did.

ls_shop.tests.test_order_refunds: 9 of 9 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
The type hints added in 69c9477 read add_node's link_target as a plain string,
but set_link runs parse_list over it for the item-group case: linking a menu
entry to several groups passes a list, and Frappe enforces the annotation at
runtime, so 30 tests raised FrappeTypeError and pydantic's string_type on a
path that had worked.

Only visible now because the missing get_order_payments had been hiding the
whole suite from CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
A bare CI site never runs the setup wizard, so 120 of the 180 failures were one
missing Company: Lifestyle Settings' four mandatory fields, the storefront item
group, the fiscal year. `before_tests` seeds them the way navgold_custom does,
with the two ls_shop traps handled — the wizard needs explicit fiscal-year dates
or Company creation silently aborts, and ls_shop makes custom_displayname
mandatory on Item Group, so the wizard's own stock groups never insert.

Also: migrate in CI, since Shop Theme records sync on migrate and not on
install-app; `frappe.db.add_column` now lives in frappe.database.schema; and the
suites that indexed [0] into Item / Style Attribute Configurator build their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Second layer under the missing company. `set_defaults_for_tests()` points the
customer group and territory at the tree roots, which are group nodes, so every
Customer the suite inserts without naming one was refused; and the cart and search
suites build their Item Prices from Lifestyle Settings' price lists, which a bare
site leaves blank.

Also seeds the themed /<lang>/page/<route> entry that Shop Web Page has always
needed: it was in no DEFAULT_ROUTES and its patch was in no patches.txt, so a
merchant's content page never resolved on a themed storefront.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
… of the run

Assigning `frappe.form_dict` rebinds the module attribute and throws away the
LocalProxy, so every later test that sets `frappe.local.form_dict` writes somewhere
nobody reads and the readers see the stale dict these two left behind. Order
dependent, which is why each module passed on its own: the sitemap segment, footer
preview, product detail and OG suites only failed in a full run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
…eb Page

The Sales Order lock was taken and never given back, so a refused refund locked the
order for good — and because the lock is a file rather than a row, it outlived the
test rollback and blocked the next order to reuse the freed name.

The footer picker still listed core Web Page docs, which render outside the
storefront theme; it lists published Shop Web Pages now, as its test always said.

Also: two more fixtures reaching for Item[0] on a site that has none, and a ladder
test calling a helper only its sibling class defines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
…age picker spec

`add_column` leaves `table_columns::` stale, so the migration under test read the
table as it was before the ALTER and found nothing to move. Frappe's own db tests
clear the key the same way.

The footer suite still asserted the picker offers core Web Pages, which the pages
suite explicitly forbids — a Web Page renders outside the storefront theme. Same
coverage, pointed at Shop Web Page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
Committed rather than attached because GitHub takes PR attachments only through
the browser; the description links to this file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbXDZ5bAG5GmPFpDUVswS3
@Rl0007
Rl0007 merged commit a9ff45e into main Sep 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants