The order page becomes the customer's area, one page at four addresses:
- /conta/entrar: sign in or create an account, side by side. "Esqueci
minha senha" points to the Dropstar WhatsApp until e-mail can be sent.
- /conta: the counts of orders waiting for payment, in production, in
correction and finished, and the latest one.
- /conta/pedidos: every order and the cart waiting for payment in one list,
newest first, filtered by group, order number and period, ten per page.
The cart shows "Aguardando pagamento" and its "Pagar" goes to the PIX
page when a PIX code is open. An order opens in place with its progress,
items, delivery, history, files and the correction form. A guest sees
the orders paid in this browser.
- /conta/dados: WhatsApp and a saved delivery address (the CNPJ is locked),
e-mail and password changes, both confirmed with the current password;
a password change signs the other devices out.
The cart fills in the account's details and saved address. New API routes
for the details, and the order list takes filters and pages and returns
the counts; only the newest unpaid quote whose files still exist is listed.
The Site's "Minha conta" and "Ver meus pedidos" point to the new addresses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The product page's notice about the upload time is gone. The cart's upload
bar now reads "Enviando seus arquivos · faltam cerca de 6 min", measured
from the upload's own speed (a range from the size for the first seconds),
with a line saying the time depends on the internet and to keep the page
open. The header and the pay button show the same time instead of a
percentage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each cart item has a checkbox and a thumbnail of its first file (or its
extension when there is none). Only checked items are priced, quoted,
uploaded and paid; an unchecked item stays in the cart, dimmed, for a later
order, and stays there after the others are paid. The header count is the
whole cart; the summary counts what is being paid.
Also removes the "A conta nasce aqui…" and "Carrinho salvo neste
navegador…" texts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Buttons: add to cart (bag with plus), go to payment (lock), remove from
the cart (bin).
- Delivery: a pin for pickup in Franca, a truck for home delivery.
- Payment: card and QR code on the methods, copy on the PIX code, a clock on
its countdown, a check on the confirmation.
- Status: a check when the files are sent, check, alert, redo and cross in
the quality list instead of Unicode symbols, and a check on finished steps.
- Kanban: a truck or pin for where an order goes, a speech bubble on the
WhatsApp link, a download arrow on the file buttons.
All Lucide shapes drawn inline, as in the header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When files are picked, the product page shows the range the upload takes
between a slow (10 Mbps) and a fast (150 Mbps) connection, and asks to keep
the page open. The cart shows the same range until the real speed is
measured, then the measured time left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The product page is rebuilt around mockup 1. The two ways to send are
cards under the title; the PNG/CDR route choice is gone (it never changed
the price, and the picker now takes every allowed format). Each file card
has a thumbnail, its size, check chips with a plain sentence for any
problem, a "Ver detalhes da conferência" link and a copies stepper; a CDR
file says "Conferência manual · preço cheio" and asks for the length. The
buy box shows the price scale in DPI with the customer's step, the metre
and 10 cm length, the total and what the resolution saved, and "Sai em N
partes" only above 20 m. Folha já montada gets "Ampliar" instead of zoom.
Pricing, grade and checks are unchanged.
Fixes on top: the card thumbnail is a small image made once, not the full
preview re-parsed on every repaint; sizes from 1 GB up read in GB; a failed
upload is shown with "Tentar de novo" instead of retrying silently on every
cart change. The browser suites wait for the product and the reloaded page
instead of racing them, and the artwork suite delays imagemDaArte, which
the packing now uses, fixing the CI failure of f4aacb5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading a large sheet repainted the list and "Sua folha" on every progress
step, so the preview flickered; progress now moves the bar in place and
"Sua folha" shows a skeleton until the preview is ready. A sheet printed
N times is shown N times (up to six, then a count). The payment page shows
skeletons while the order loads and until the card form is ready.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Uploads of several GB ran behind one grey line in the cart. The order
summary now has a progress bar with the percentage, size and an estimate of
the time left from the recent speed; the header shows "enviando 45%" on
every page while it runs; the payment button says what it is waiting for;
and leaving the page mid-upload asks first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A credit card payment with the test credentials was refused with 10113
("the payment method is excluded by a rule"). Every card was sent with
three_d_secure_mode, which only debit needs, and with the order's CNPJ as
payer instead of the cardholder's document from the card form. Debit
methods keep 3-D Secure, and the card form's document is the payer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sheets of several GB are the normal order. The upload limit is now 5 GB.
ClamAV scans files up to 2 GB; a larger file is released only when its
first bytes match the format its name claims, and a disguised file is
refused. The Site grades a sheet over 150 MB from the pixel size in its
PNG, JPEG or WebP header without decoding it, and reads large PDFs in
ranges. The worker never opens a source over 300 MB: a finished sheet
placed whole becomes its own print file, which the Kanban offers to approve
as the final, and anything else goes to hand preparation. Files start
uploading as they enter the cart, with progress in the summary, and each
part renews the reservation so slow uploads do not expire. Quotas grow to
50 GB per customer and 500 GB in total; the Swarm config for ClamAV is
renamed because a deployed config cannot change in place.
Verified locally with a 386 MB and a 1.8 GB PNG (scanned, paid, original
as print file), a 2.3 GB PNG (format check) and a disguised 2.3 GB file
(refused).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When creating the card payment failed, the form's onSubmit rejected with no
message and Mercado Pago's button kept spinning. The page now shows the
reason above the form. A payment Mercado Pago refuses is logged with its
status, message and cause codes (payment_intent_refused) and answered 422
with that reason; other failures log their type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mercado Pago's form titled both card options "Cartão de crédito ou débito";
the option above it already names the card.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A complete CEP now looks up its address on the server (ViaCEP, rate
limited, keeping the Site's CSP to its own origin) and fills street,
district, city and state, moving the cursor to the first field left, and
quotes the freight without pressing Calcular. The CEP keeps its mask when
the cart is restored. Freight and its delivery time appear in the order
summary instead of a line under the CEP, the delivery option says it ships
with Jadlog, and the address reminder is a hint rather than an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FREIGHT_ADAPTER=jadlog prices "Receber em casa" through Jadlog's Simulador
de Frete from the order's billed metres and value, adding production days
to Jadlog's delivery time. The package weight is a base plus a weight per
metre from the client, with no default: the adapter refuses to start
without it and without the credentials. The cart re-quotes when the package
changes, and approval quotes again from the server-priced items. The
production stack takes the Jadlog settings, so the read-only probe runs
from the worker's console.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PIX is created with a 30-minute date_of_expiration, and the PIX page
counts down to it. When it runs out the page says the code expired and
offers a new one. The API keeps one open code per quote: the same code
until it expires, then exactly one new attempt (serialised per quote, with
its own idempotency key), the old one marked expired.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The payment page lists credit card (preselected), debit card and PIX. Each
card option limits Mercado Pago's form to its kind; debit is paid at once.
Card payments ask for 3-D Secure when the issuer requires it, and a
challenge opens the bank's page in a frame, which needs
PAYMENT_CHALLENGE_SOURCES=https: (frames and form posts only). A card left
waiting for that confirmation stops blocking a new attempt after ten
minutes, and a refusal reported by the notification returns the customer to
the payment choice. Written from the documentation; not yet run with a real
debit card.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The payment page puts paying on the left and the order summary on the
right (above it on phones). Card is preselected and paid on the page with
Mercado Pago's form, in the Site's colours and with the order's e-mail;
choosing PIX shows a Pagar button that opens /pagamento/pix with the QR code
and copy-and-paste code, waiting there for the confirmation. A confirmed
payment shows "Pagamento confirmado" with the order number and a button to
the customer's orders. The payment buttons no longer restyle every button
inside the form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The summary above already shows the total; the line stays only beside the
local stack's simulated payment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PIX and card choices appeared under the cart, on the same page as the
customer's details. "Ir para o pagamento" now sends the order and opens
/pagamento, step 3 of the progress bar: the server's order summary, then PIX
or card, each opening below. The cart keeps only the sending progress and its
errors; a changed cart is sent again instead of offering the old quote.
Portal links open the payment page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The home, each product's Montagem and the cart now have their own
addresses (/artes-avulsas, /arquivo-por-metro, /uv-artes-avulsas,
/uv-arquivo-por-metro, /carrinho) and show only their own content, with
Back, Forward, reload and direct links working as in any store. They stay
one document so uploaded artworks survive moving between pages; nginx
serves index.html for these addresses.
"Adicionar ao carrinho" puts the item in the cart and opens it, and an
empty cart says so. Portal quote links open in the cart. Also fixes the
"57 cm" line break on the ready-sheet option, returns "Novo pedido" to
the home, and says PDF depends on the product.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kanban: tabs for production, quote review and payments/integrations; compact
cards with products, metres, print-file status, delivery and time in stage;
an order panel with stage progress, one main action, a correction reason in
place, items with a preview drawn from the approved layout, final-file
approval and the history as a timeline. Quote review gets a list and a pane;
payment issues resolve in place; integrations show their real state, Tiny's
connection with a read-only "Testar conexão", and a readable send log. The
previous Kanban is kept in git tag ui-v1 and is no longer served.
Production wording: the customer portal no longer says it is a local test
environment outside the local stack; the checkout no longer tells customers
to use the Kanban or shows internal stage codes; sign-in, session, quota and
print-file messages are Portuguese and never say "local". A simulated freight
price is refused outside the local stack until a real freight provider
exists, so production only offers pickup.
The browser suite drives the new tabs and panel and still checks the whole
upload, quote, payment and production journey. Full CI sequence passes locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDF artwork: a single-page PDF source is placed in the print file as a
vector form through pikepdf, never rasterised, using the CropBox and
inherited /Rotate the Site measured with pdf.js. Multi-page and protected
PDFs go to hand preparation. PyMuPDF was not used because of its AGPL
licence. Raster tests cover crop, page rotation, placement rotation and
mirroring, and fail when the rotation or crop handling is broken.
Card payment: Mercado Pago's Card Payment Brick on the Site when
MP_PUBLIC_KEY is set; the card becomes a one-time token in Mercado Pago's
secure fields. Each card attempt has its own idempotency key, and the intent
route refuses new attempts once a payment is approved or a card is in
review, so a quote cannot be charged twice. The Site CSP admits Mercado
Pago's origins only through PAYMENT_CSP_SOURCES, empty by default.
Logins: every attempt counts against the source address, only failures
against the account. Counting successful sign-ins let ordinary use lock an
operator out and made CI's final browser sign-in fail.
No new required settings; production behaviour is unchanged until the
provider credentials are configured. Verified with the full CI integration
sequence locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Week 2 work that did not need client inputs.
Print files (1.4): each paid item gets a PDF the width of the film and the
length of the approved layout, with every copy at its reviewed position,
rotation and mirror. Sources are embedded once at original resolution; JPEG
bytes pass through and PNG alpha becomes a soft mask. Artwork the generator
cannot reproduce goes to hand preparation with the reason. The worker renders
outside any transaction, and the operator approves the generated file as the
final one through the existing review.
Delivery address (3.8): required for any non-pickup quote, bound to the
quoted CEP, carried into the order snapshot, the Kanban card and Tiny.
Kanban (1.5): print-file status per item, and a panel of payment events that
need a person (money without an order, refunds after an order) until an
operator records the resolution.
Mercado Pago and Tiny (1.1, 1.3): adapters written from the public API
documentation and tested against fake transports only. Selectable for
sandbox testing with their credentials; the production preflight still
blocks release. Adds payment intents and a PIX step on the Site.
MinIO: Docker Hub and quay.io now refuse anonymous pulls, so local and CI
storage use Chainguard's MinIO build, pinned by digest.
Verified with the full CI integration sequence on a fresh local build,
including the new print_file_test and both browser suites.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Site's page sat at the repository root while its scripts lived in
local/static, a split with no reason behind it. They are together in web/ now,
with the page as index.html, which is also what the image serves.
app.py held the adapters, the configuration, the shared query helpers and
nineteen routes; customer.py held fourteen more but could not import from it
without a cycle, so it was wired by passing nine callables into install_routes.
Configuration and shared helpers move to local/runtime.py, the rules for
attaching artwork to an order move to local/artwork.py where a customer
correction and an operator final-file set can share them, and the routes become
seven routers under local/api. app.py is 48 lines that create the application,
apply the middleware and include them. Routers import downwards only.
Three faults came out of the extraction and are worth recording, because each
passed a check that looked sufficient. ast reports a function's line at the def,
so every decorator on the line above fell outside the extracted range: twelve
routes and the security middleware were defined but never registered, and the
files still imported and parsed cleanly. Names the old closure renamed on the
way in, and a Jsonb import, were missing in three modules. A name-resolution
pass over every new module found those; the route count matching the original
exactly, 32, is what confirmed the first.
The release gate's marker for the fake payment adapter pointed at app.py and the
adapter moved to runtime.py, so the gate passed while the condition it guards was
unchanged. That is the same silent decay 2.5 set out to fix. A test now asserts
every marker still matches something in its file, so the next move fails loudly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>