Commit Graph

96 Commits

Author SHA1 Message Date
Cauê Faleiros
15abd589a8 ci: download the vulnerability database once, and tell a failed scan from a finding
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 55s
The CRITICAL scan of dtf-api failed on a 404 from the database mirror and
was reported as a CRITICAL vulnerability. The image step now downloads the
database once into a cache volume, with three attempts, scans all four
times from it, and exits 5 only on findings: a scan that does not run fails
with its own message, and nothing is published unscanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:10:58 -03:00
Cauê Faleiros
32c060e1b0 fix: hide the card form's own title on the payment page
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m39s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m50s
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>
2026-09-29 11:04:37 -03:00
Cauê Faleiros
6f5d183365 fix: show the real freight status on the Kanban
Some checks failed
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m16s
Build and deploy / Secret scan and release gate (push) Successful in 18s
Build and deploy / Publish images (push) Failing after 1m25s
The Integrations tab always said freight was not configured. It now reads
the server's adapter and, with Jadlog on, shows the package weight rule and
the production days the Site prices with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:57:34 -03:00
Cauê Faleiros
2215da8d5e fix: centre the arrow in the upload box; record that CDR/AI/PSD stay manual
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m24s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m43s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:49:11 -03:00
Cauê Faleiros
88e65290e1 fix: quote freight on its own, and drop the Calcular button
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m20s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Going back to home delivery with a CEP already typed, or restoring a saved
cart, now quotes the freight again by itself; before, only pressing Calcular
did. With every case covered, the button is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:40:55 -03:00
Cauê Faleiros
690b15ece1 feat: fill the delivery address from the CEP and quote freight as it is typed
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m21s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m47s
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>
2026-09-29 10:31:41 -03:00
Cauê Faleiros
e20c8673bb fix: send the Jadlog account exactly as configured
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m33s
Jadlog issues the account as 000000-0 and documents a six-character field;
stripping the dash sent seven digits. The value now goes as typed, so the
accepted form can be found on the account without a new release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:00:44 -03:00
Cauê Faleiros
4de2151e9a feat: price home delivery with Jadlog
All checks were successful
Build and deploy / Validate source (push) Successful in 1m18s
Build and deploy / Integration suite on a real stack (push) Successful in 2m33s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images (push) Successful in 1m42s
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>
2026-09-29 09:34:01 -03:00
Cauê Faleiros
641aafc87d feat: PIX codes expire after 30 minutes, with a countdown
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 2m50s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m54s
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>
2026-09-28 15:40:26 -03:00
Cauê Faleiros
875a7ef9c7 fix: drop the change-method button from the PIX page
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m10s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m37s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:18:18 -03:00
Cauê Faleiros
c436936fed fix: drop the confirmation-time promise from the PIX note
Some checks failed
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:15:08 -03:00
Cauê Faleiros
0ed6de4bd5 fix: shorten the PIX note on the payment page
Some checks failed
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Publish images (push) Has been cancelled
Build and deploy / Secret scan and release gate (push) Has been cancelled
Build and deploy / Integration suite on a real stack (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:13:20 -03:00
Cauê Faleiros
eae3dad306 feat: offer credit card, debit card and PIX, with the bank's 3-D Secure step
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m31s
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>
2026-09-28 13:24:06 -03:00
Cauê Faleiros
7b6675d4d4 feat: two-column payment page with card by default and PIX on its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m19s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m38s
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>
2026-09-28 13:10:05 -03:00
Cauê Faleiros
4df74221d2 fix: drop the validated-total line from the payment page
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m10s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m44s
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>
2026-09-28 12:15:00 -03:00
Cauê Faleiros
43043c361c feat: give payment its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m12s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m35s
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>
2026-09-28 11:45:44 -03:00
Cauê Faleiros
536510b148 fix: version the Site's scripts by content so a release never meets a cached old one
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 2m9s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m44s
The proxy in front of production caches .js and .css for hours. After the
last release the Site got the new index.html with the old site-flow.js,
which wrote to an element the new page no longer has; the error left
"Adicionar ao carrinho" disabled. The web build now addresses every local
script and stylesheet by a hash of its content, replacing the hand-kept
?v= markers, so a new release always loads its own files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:27:55 -03:00
Cauê Faleiros
a1877bdf0a fix: remove the pickup and invoice notes from checkout; document R2 CORS
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m15s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m41s
The pickup notice under the delivery options and the invoice and retention
note under the purchase summary are gone. PORTAINER.md records the R2 CORS
policy the browser's direct uploads need: without it the preflight is
refused and checkout fails with a NetworkError before payment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:10:52 -03:00
Cauê Faleiros
275ebf72c4 feat: approve priced carts at checkout so customers can pay at once
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m34s
Every quote waited for an operator before it could be paid, so an order
placed at night waited for the morning. A cart the Site priced is now
approved when the quote is created, through the same server pricing the
operator's approval uses (app/quote_review.py). Orders above
QUOTE_AUTO_MAX_METRES (50 m) and items claiming a discount on art the Site
could not analyse still wait for review; the Kanban shows which quotes were
approved automatically and why the others wait.

The grade is still computed in the browser (roadmap 3.2, 3.9), so the
discount remains a customer-supplied value until the server computes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:02:56 -03:00
Cauê Faleiros
9bea187ea4 feat: add a read-only Jadlog price probe
All checks were successful
Build and deploy / Validate source (push) Successful in 1m20s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 10s
Build and deploy / Publish images (push) Successful in 1m51s
app/jadlog.py prices one package through Jadlog's Simulador de Frete as the
API manual v2.3 describes it; app.jadlog_probe prices test weights to six
regions on the client's account to confirm token, account and contract.
Tested against a fake transport. The roadmap records the Jadlog data and the
Mercado Pago account setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:41:18 -03:00
Cauê Faleiros
bd56170248 feat: let production select Mercado Pago and acknowledge simulated notifications
The production compose hard-coded the fake payment adapter; it now takes
PAYMENT_ADAPTER and the MP_* settings from the stack's environment, so the
sandbox can run with test credentials. A signed notification about a payment
Mercado Pago does not have, such as the panel's "Simular notificação", is
acknowledged instead of answering 500 and being retried; any other lookup
failure still raises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:41:18 -03:00
Cauê Faleiros
60b7336b37 docs: commit the operator guide's source and record the Week-2 report
The operator guide is now built from docs/guias/operador/ by
docs/guias/imprimir.sh, with the corrections on Tiny and the WhatsApp
notices. The roadmap records the guide, the history fix found while
writing it, and the Week-2 report as sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:07:58 -03:00
Cauê Faleiros
04e4cbc953 fix: hide operators' internal back-move reasons from the customer's order history
A move back undoes an operator's mistake and its reason is internal. The
customer's history now omits back moves and shows a reason only for a
correction; the smoke test checks both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:07:58 -03:00
Cauê Faleiros
aec5b1d054 feat: send order situações to Tiny for the client's WhatsApp notices
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m5s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m45s
The client already sends WhatsApp notices from Tiny's order situação
(Tiny webhook -> middleware -> n8n). With TINY_STATUS_UPDATES on, a paid
order is set to "Aprovada" once and a finished pickup order to "Pronto
para envio"; pickup orders carry the client's pickup forma de envio
(TINY_FORMA_ENVIO_RETIRADA). The ready event now carries the order and
the Tiny id from the sale's receipt. Off by default until go-live, when
n8n stops sending the DTFIMP designer message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:17:00 -03:00
Cauê Faleiros
8b42e684ca docs: record the Tiny account and product findings
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 2m46s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:40:28 -03:00
Cauê Faleiros
e768dcb489 feat: keep the Tiny connection alive and show when it is not
All checks were successful
Build and deploy / Validate source (push) Successful in 8s
Build and deploy / Integration suite on a real stack (push) Successful in 2m29s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m49s
Connecting now asks for offline_access, retrying once without it if Tiny
refuses the scope. Renewal failures are stored: a refused refresh token
marks the connection lost and is not sent again (the Kanban previously
still said "conectado"), a transient failure shows as a warning until the
next renewal, and a session grant with under 12 hours left is flagged.
Tiny errors on the callback return to the Kanban instead of a 422.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:04:23 -03:00
Cauê Faleiros
122c645f72 fix: stop Site timers from running after a product is closed
The grade check and the layout preview run on short timers. When the
item went to the cart inside that window, no product was open and both
threw in the customer's browser, which also failed the browser tests
intermittently. Each now returns when no product is open. The cart test
waits for the empty state, which is painted on the next animation frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:04:23 -03:00
Cauê Faleiros
988b252f9d feat: check Tiny products and add a supervised order test
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 2m39s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m43s
"Testar conexão" now also reads the four configured Tiny products and
requires each to be active. app/tiny_probe.py runs from the worker console
to list products, confirm the configured ids, and create one marked test
order through the worker's own delivery path, proving the duplicate guard
by search before a second delivery. Nothing is sent without --confirmar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:23:07 -03:00
Cauê Faleiros
56021d8451 docs: add the operator and Site guides and bring the roadmap up to date
All checks were successful
Build and deploy / Validate source (push) Successful in 1m39s
Build and deploy / Integration suite on a real stack (push) Successful in 2m32s
Build and deploy / Secret scan and release gate (push) Successful in 10s
Build and deploy / Publish images (push) Successful in 1m58s
The operator guide covers the Kanban flow from quote review to finished
order; the Site guide walks through the customer journey, prices and the
current state of each integration. The roadmap records the Kanban and Site
redesigns, the guides, and what is left for the last day of Week 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:07:50 -03:00
Cauê Faleiros
b6a90411f1 feat: let customers remove items and empty the cart, with undo
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m2s
Build and deploy / Secret scan and release gate (push) Successful in 4s
Build and deploy / Publish images (push) Successful in 1m33s
Each cart item has a visible "Remover" button instead of a faint ×, and
carts with two or more items get "Esvaziar carrinho". Both show a
"Desfazer" notice for 8 seconds, so a wrong click costs nothing. The
browser test covers remove and undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:18:10 -03:00
Cauê Faleiros
cbbbdb351a feat: rebuild the Site product page around a buy box
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 2m46s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m46s
Artworks on the left; on the right a box that stays in view with the live
sheet, the grade, the price per metre, the metres charged, the total, the
resolution note and "Adicionar ao carrinho". The separate quality and
preview panels, the second sheet preview, the per-row mini sheets, the
summary box and the repeated findings list are gone: each piece of
information now appears once.

Each artwork row carries at most one hint (resolution first, otherwise a
width that saves film), the ready-sheet/loose-artwork choice is a toggle
beside the title, the upload area is one bar and the tips are collapsed.
The box only shows the item the page already priced, so it always matches
the cart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:02:45 -03:00
Cauê Faleiros
b81ff8d03d feat: give each Site product and the cart its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m28s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m52s
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>
2026-09-24 16:56:58 -03:00
Cauê Faleiros
177882e77b feat: redesign the Site order flow and fix the cart layout
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m23s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m48s
New landing (hero with a sheet preview, four steps, product cards priced
from the checkout table, price table and benefits), a progress bar that
follows the order through Montagem, Dados e entrega and Pagamento, the
live sheet beside the artworks, and a running total bar on phones.

The cart's saved-in-browser note no longer takes a grid column, which had
pushed the order into a narrow strip and the summary below it. The
previous look is kept in tag ui-v1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:33:06 -03:00
Cauê Faleiros
2e03b0362b feat: undo mistaken moves, numbered pagination, and quieter Kanban messages
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m33s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m48s
Moves: an order can go back one stage (BACK in app/runtime.py) with an
internal reason, flagged in the history as movements.back. The customer is
not notified and approved finals stay; "production started" and "ready" are
now enqueued once per order, so undoing and redoing a move sends nothing
twice. Dragging only goes forward and highlights the allowed column. Move
errors are in Portuguese.

Lists: the send log, payments (open, resolved as history, all) and quotes
are paged on the server with a total, 20 rows by default (10/20/50/100),
first/previous/page/next/last. The send log filters by destination, status,
event and order. Older finished orders load on demand. The board no longer
carries the send log or payment rows, only the open-payment count.

Kanban: Pagamentos and Integrações are separate tabs; messages are brief,
bottom notifications that clear themselves; wording is shorter.

Full CI integration sequence passes locally, with new checks for undo, paging
and filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:01:31 -03:00
Cauê Faleiros
99a558ddd9 feat: redesign the Kanban and keep test wording out of production
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 3m3s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images (push) Successful in 1m51s
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>
2026-09-24 15:28:04 -03:00
Cauê Faleiros
641dc6053b ci: publish images from every green push to main
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m5s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m54s
The release job enforced the production source preflight, which blocks while
the payment and messaging adapters are fake. They still are, by design, so
since 2026-09-23 no release could succeed and production kept running older
images while main moved on.

Pushes to main that pass validation, the integration suite and the scans now
build, scan and publish the images. Nothing is deployed automatically:
production changes when the stack is pulled and redeployed in Portainer. A
manual run also calls the Portainer webhook when one is configured. The
preflight stays in the scan job, advisory unless ENFORCE_PRODUCTION_PREFLIGHT
is true, in which case a blocked preflight stops publishing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui-v1
2026-09-24 14:30:43 -03:00
Cauê Faleiros
4c01e932c3 feat: place PDF artwork in print files, add card payment, count only failed logins
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m23s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
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>
2026-09-24 13:14:56 -03:00
Cauê Faleiros
e3d5558198 feat: connect Tiny through its v3 API with OAuth
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
Tiny v3 replaces the v2 token adapter. An operator connects Tiny once from
the Kanban; the callback is authorised by a single-use state, because Tiny's
cross-site redirect does not carry the SameSite=Strict operator cookie.
Tokens are kept in provider_tokens, the refresh token rotates under a row
lock, and the worker keeps the connection alive while order creation is off.

Orders find or create the customer's contact by CNPJ, then POST /pedidos
with product ids from TINY_PRODUCT_TEXTIL_FOLHA, _TEXTIL_AVULSA, _UV_FOLHA
and _UV_AVULSA and numeroOrdemCompra DTF-<number>; a retry searches the
customer's recent orders for that number first. The product settings avoid a
_FILE suffix, which the secrets loader reads as a secret file path.

Production passes the application credentials through but keeps
TINY_ADAPTER fake: Tiny has no sandbox, so creating real orders waits for a
supervised test. compose.providers.yaml gives the local API and worker an
internet route for provider testing; the default local stack still has none.

Verified with the full CI integration sequence locally, including the new
tiny_oauth_test against the real database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:46:09 -03:00
Cauê Faleiros
c18b9e5b87 feat: generate print files, collect delivery addresses, add provider adapters
All checks were successful
Build and deploy / Validate source (push) Successful in 1m45s
Build and deploy / Integration suite on a real stack (push) Successful in 4m48s
Build and deploy / Secret scan and release gate (push) Successful in 11s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
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>
2026-09-24 11:56:46 -03:00
Cauê Faleiros
cfcbe545f1 ci: require manual gated releases from main
All checks were successful
Build and deploy / Validate source (push) Successful in 1m28s
Build and deploy / Integration suite on a real stack (push) Successful in 4m3s
Build and deploy / Secret scan and release gate (push) Successful in 11s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
2026-09-23 11:27:18 -03:00
Cauê Faleiros
24013458c9 fix: harden week-two ordering, artwork and operations 2026-09-23 10:40:18 -03:00
Cauê Faleiros
ccc25a2d5d feat: accept payment notifications, once, from a verified sender
There was no inbound payment path at all: a button called a fake synchronously
and wrote an order. A real provider does the opposite — it charges, then tells
us, repeatedly, out of order, and sometimes long afterwards.

POST /api/payments/webhook verifies the signature before the body is parsed, so
an unsigned or tampered delivery is refused and recorded without touching an
order. Verified deliveries are stored under the provider's own event id with a
unique constraint, and applied inside the same transaction that marks them
processed: a repeat is a no-op, a crash is retried rather than half-applied.

An approval whose amount disagrees with the reviewed quote does not become an
order. Underpayment would ship artwork nobody paid for, and overpayment means
something a person should look at.

Order creation moved to app/payments.py so the webhook and the local development
checkout share one implementation and cannot drift. That also closes 3.5: the
charge happens inside the transaction that persists the order, rather than
before it.

The adapter contract is create/verify/parse. FakePayment implements it with a
real HMAC scheme so the whole path is exercised now, by tests/payment_test.py:
unsigned, tampered, underpaid, duplicate, re-sent, unknown reference, and
non-approved statuses. Connecting Mercado Pago is one adapter; no service code
changes.

PAYMENT_WEBHOOK_SECRET is optional in production on purpose. Required would
break the next Portainer render, and a guessable default would be worse than
either: with no secret configured the adapter verifies nothing and therefore
accepts nothing, which is the right state until a provider is connected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:25:08 -03:00
Cauê Faleiros
7386469404 docs: move the engineering documents into docs/
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 1m18s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m26s
Thirteen files at the repository root, seven of them documents. Only README.md
earns a place there; the rest are now in docs/ beside the meeting notes, the
client roadmap and the historical material.

The compose files stay. docker-compose.yml is the path the dtf-cloud Portainer
stack reads, so moving it would break deployment, and Docker resolves a compose
file's relative build contexts against its own directory, so moving the other
two would silently break every build. Both reasons are now written down where
someone would otherwise try it.

Correcting references turned up a live fault: the Portainer stack creation
instructions still named deploy/stack.yaml as the compose path. That file was
removed, so anyone recreating the stack from these instructions would have
failed. It names docker-compose.yml now, with the reason it stays at the root.

ROADMAP.md keeps the paths its closed findings were written with, and says so at
the top. Those entries record where a fault was when it was found; rewriting
them to match a later layout would make the record less true, not more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 17:52:20 -03:00
Cauê Faleiros
b329f76378 refactor: lay the repository out by role
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 1m25s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m31s
local/ held six unrelated things under a name that stopped being true once it
became the production runtime: the service, the frontend, the tests, the ops
commands, the container definitions and the dependency lock, 65 files with
nothing to tell them apart.

  app/      the service: api/ routers, core/ for identity, database, models,
            prices and secret loading, and the worker, bootstrap and schema
  tests/    the twelve suites, no longer inside the shipped package
  ops/      backup, readiness, dependency audit, security summary
  infra/    Dockerfiles, gateway templates, ClamAV and storage configuration,
            the requirements and their hash lock
  web/      the Site, Kanban and portal pages with their scripts

deploy/Dockerfile.api now copies app/ alone, so the tests stop shipping to
production; the local image still carries them, because the suites run inside
the stack's network.

Five kinds of reference had to follow, and each was found by something different
rather than by reading. Imports of the form "from . import db" survived a rewrite
that only matched "from .db import". Tests kept relative imports of modules that
had left the package. A mock.patch target names its module in a string, where no
import rewriting can see it. The browser test resolves a fixture by path. And the
release gate's markers pointed at local/runtime.py and local/worker.py, which is
the decay its new marker test exists to catch — it caught it.

Verified from docker compose down -v: the stack starts, all six integration
suites, both browser suites and the twenty-nine unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 17:40:57 -03:00
Cauê Faleiros
c9f8122600 refactor: give the frontend its own directory and split the API into routers
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>
2026-09-21 17:22:00 -03:00
Cauê Faleiros
66ddb17f02 chore: untrack deliverables committed by mistake, and home the stray files
All checks were successful
Build and deploy / Validate source (push) Successful in 10s
Build and deploy / Integration suite on a real stack (push) Successful in 1m47s
Build and deploy / Secret scan and release gate (push) Successful in 7s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m38s
The previous commit used git add -A and swept in four binaries that were
deliberately untracked: the week-1 client report as .docx and .pdf, a duplicate
of it under output/documents, and imagem-teste.jpg, an input dropped in to test
with. None of them are the repository's to version. They are untracked here,
left on disk, and covered by .gitignore so the mistake cannot repeat.

Three files had no sensible home. The meeting notes sat at the repository root
under a 78-character name with spaces and accents, the roadmap generator lived
in tmp/ — a directory otherwise ignored as scratch — and its output in output/,
which is otherwise generated evidence. They are now docs/reuniao-2026-09-09-
anotacoes.pdf, tools/generate_dtf_report.py and docs/roadmap-cliente.pdf, with
CONTEXT.md and ROADMAP.md updated to match and a docs/README.md saying what each
document is for.

.gitignore no longer needs four rules to keep one generator out of an ignored
directory; tmp/ is scratch again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 16:47:36 -03:00
Cauê Faleiros
96f1d27221 refactor: split the Site's behaviour out of one 1,575-line inline script
dtf-site.html held commercial rules, the nesting engine, PDF analysis, the cart
and every handler in a single inline script, 42% of the runtime code in one
file, and the money logic lived in the middle of it.

It is now nine files under local/static, cut at the section markers the original
author left, so no function was split across a boundary: config, product modes,
upload, sheet analysis, PDF, quality, packing, cart, flow. They load as classic
scripts in the original order and share one global scope, so evaluation is
exactly what it was; the extraction was checked byte-identical against the
original before the tags replaced it. dtf-site.html is 1,394 lines of markup and
style.

With no inline script left anywhere, the policy no longer needs a hash
allowlist: script-src is now 'self' alone, which is stronger than what it
replaced and cannot drift as the page changes.

Three things depended on the old shape and were updated rather than worked
around. The pricing parity test read the ladder out of the HTML and now reads it
from site-config.js, still proving the server agrees with what the customer is
shown. The isolated artwork test served four hardcoded script paths and now
serves any script that resolves inside local/static, so the next file added does
not silently 404. The CSP assertion checked the whole policy for 'unsafe-inline'
and now checks the script-src directive alone, since style-src legitimately
carries it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 16:44:49 -03:00
Cauê Faleiros
ca698434a2 chore: remove the abandoned prototypes and archive what described them
portal/, kanban/ and agente/ were 2,034 lines implementing the original
Tiny-first model: token upload links, a second SQLite Kanban, a factory agent.
Nothing imported or started any of it, and several endpoints took the acting
user from the request body with no authentication at all. Their real cost was
that a reader arriving at this repository found two Kanbans and two portals and
had to work out which one was real. The root schema.sql and .env.exemplo went
with them: both code paths load local/schema.sql, and having .env.exemplo beside
.env.example differing by one letter was a trap rather than a convenience.

The documents describing that model are archived rather than deleted. They
record decisions and reasoning the current documents do not repeat, so they are
worth keeping as background, with a header saying plainly that they are not
instructions.

README.md keeps its business case — the capacity figures and the cost argument
are still the reason this project exists — but now states where the prototype
documentation begins and that the code it describes is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 16:34:20 -03:00
Cauê Faleiros
86b8199612 fix: create indexes after the tables they name
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 1m20s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m37s
The 4.1 indexes were added beside the existing uploads_owner, which sits partway
through schema.sql, so CREATE INDEX ... ON dtf_local.order_files ran before that
table was created and bootstrap aborted with UndefinedTable. db-init then
restarted on failure without ever completing, and everything waiting on it timed
out.

Every local run passed because those volumes already had the tables. Only a
clean database exposes it, which is what CI has and my checks did not.

All eleven indexes now sit at the end of the file, after every table, with an
assertion in the change that each indexed table is created before its index.
Verified from docker compose down -v: the stack starts, bootstrap completes,
eleven indexes exist, and the full suite passes.

Recorded as ROADMAP 5.11: nothing exercises the schema against an empty
database, which is the only way this class of fault appears.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 15:00:10 -03:00
Cauê Faleiros
543a9a9fb4 perf: index the real queries, bound the board, and correct what the Site promises
Some checks failed
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Failing after 10m30s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
Five items that needed no decisions.

Indexes: the schema indexed only uploads(owner), so the worker's once-a-second
outbox poll scanned a table that only grows, and every per-customer and
per-order lookup did the same. Ten indexes now follow queries the application
actually issues, and no more, since each one is paid for on every write. The
outbox and live uploads use partial indexes so they stay the size of the backlog
rather than of all history. Confirmed against the database that the planner
chooses them.

Board: /api/operator/board returned every order ever created. Finished orders
are terminal, so they were pure growth. It now returns everything still in
progress however old, plus a window of recent finished ones and the true
finished total, and the Kanban column says "50 de 213" rather than letting the
count read as an all-time figure. An operator cannot lose a card they could act
on.

Dependencies: the root requirements.txt was the prototype's, pinned by wildcard,
listing packages this system does not use, next to the hash-locked lock file.
Deleted. pip was pinned as a runtime dependency, which installed a package
manager into the read-only production image; nothing depended on it, so it is
gone from both the direct list and the lock, and the base image's pip performs
the hash-enforced install.

Retention copy: the Site told customers their artwork was kept 90 days with 12
months of history, and invited them to reorder without uploading again. Files
are kept 30 days. The copy now matches the policy and drops the promise the
system cannot keep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 14:29:07 -03:00