From 7eb8a11d0229c4ec7011e3042727fab930260590 Mon Sep 17 00:00:00 2001 From: iscarelli Date: Fri, 7 Aug 2026 17:27:17 -0300 Subject: [PATCH] docs(changelog): translate release notes from 1.36.0 onward to English English is now the source language for release notes, matching commits and PRs. The Updates page (templates/admin/update.html:48) renders the notes body verbatim with no notion of language, so an admin running the system in English or Spanish was handed a block of Portuguese inside a translated frame. Entries from 1.36.0 to 1.39.1 (14 of them) are translated faithfully: same structure, same sections, same items and order, with versions, file paths, route names and code identifiers left literal. Nothing was reworded, merged, added or dropped. Entries below 1.36.0 stay in Portuguese behind an explicit boundary marker. They are historical record; retranslating them would risk altering what was actually written at the time. No VERSION bump: this is a documentation fix, not a release. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 326 ++++++++++++++++++++++++++------------------------- 1 file changed, 169 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a8f2e..d1d584f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,220 +13,232 @@ Versioning follows [SemVer](https://semver.org/): **MAJOR.MINOR.PATCH** ## [1.39.1] — 2026-08-07 ### Fixed -- **Os rótulos de valor vazio dos relatórios agora aparecem traduzidos.** Quem usava o - sistema em inglês ou espanhol via `(sem marca)`, `(sem material)` e `(sem local)` em - português no meio da tela — agora são `(no brand)` / `(no material)` / `(no location)` - e `(sin marca)` / `(sin material)` / `(sin ubicación)`. Afeta os relatórios "Histórico - de Consumo" e "Estoque por Local". -- **`GET /api/stock` deixou de devolver rótulo de tela.** Um rolo sem local agora vem - com `location` em branco, em vez do texto `(sem local)`: a API é lida por máquina - (Home Assistant), e texto de interface em português não tem lugar nela. +- **The empty-value labels in the reports now show up translated.** Anyone using the + system in English or Spanish saw `(sem marca)`, `(sem material)` and `(sem local)` in + Portuguese in the middle of the screen — they are now `(no brand)` / `(no material)` / + `(no location)` and `(sin marca)` / `(sin material)` / `(sin ubicación)`. Affects the + "Consumption History" and "Stock by Location" reports. +- **`GET /api/stock` no longer returns a display label.** A spool with no location now + comes back with a blank `location` instead of the text `(sem local)`: the API is read + by machines (Home Assistant), and Portuguese interface text has no place in it. ## [1.39.0] — 2026-08-07 ### Added -- **Novo relatório "Histórico de Consumo"** (menu Relatórios), que responde duas - perguntas que o sistema não sabia responder: *quantos rolos já gastamos* e *quanto - filamento saiu por mês*. A tela mostra o total de rolos finalizados, o total consumido - em quilos, o período coberto, uma tabela mês a mês e as quebras por material e por - marca. Dá para ver os últimos 12 meses ou o histórico inteiro. -- **Rolo finalizado agora guarda a data.** Até aqui, finalizar um rolo só o tirava da - lista de ativos e o "quando" se perdia — havia um total, nunca um histórico. A partir - desta versão a data é registrada no momento em que o rolo é finalizado. - -### Limitações declaradas na própria tela -- **Rolos finalizados antes desta versão não têm data registrada**, então o sistema a - estima a partir da última pesagem de cada um. Essas datas aparecem marcadas com **≈**: - são um limite inferior (o rolo acabou em algum momento dali em diante), não a data - real. Rolo finalizado que nunca foi pesado não recebe data inventada — ele é contado à - parte, na linha "Sem data". -- **O total em quilos só enxerga rolos que foram pesados**, porque o consumo é medido - pela diferença entre duas pesagens do mesmo rolo. Um rolo nunca pesado aparece na - contagem de rolos e não soma nada aos quilos. +- **New "Consumption History" report** (Reports menu), which answers two + questions the system could not answer: *how many spools have we already used up* and + *how much filament went out per month*. The screen shows the total of finished spools, + the total consumed in kilos, the period covered, a month-by-month table and the + breakdowns by material and by brand. You can see the last 12 months or the whole history. +- **A finished spool now stores the date.** Until now, finishing a spool only took it out + of the active list and the "when" was lost — there was a total, never a history. As of + this version the date is recorded at the moment the spool is finished. + +### Limitations stated on the screen itself +- **Spools finished before this version have no recorded date**, so the system + estimates it from the last weighing of each one. Those dates appear marked with **≈**: + they are a lower bound (the spool ran out at some point from there on), not the real + date. A finished spool that was never weighed does not get an invented date — it is + counted separately, in the "No date" row. +- **The total in kilos only sees spools that were weighed**, because consumption is + measured by the difference between two weighings of the same spool. A spool that was + never weighed shows up in the spool count and adds nothing to the kilos. ## [1.38.7] — 2026-08-07 ### Fixed -- **Depois de atualizar, a novidade não aparecia até limpar o cache do navegador.** - O navegador continuava usando o JavaScript e o CSS da versão anterior, que ele - já tinha guardado — a tela abria com o comportamento antigo e nada indicava que - havia algo desatualizado. Quem testasse uma modificação recém-publicada concluía, - com razão, que ela não tinha funcionado. Agora cada arquivo de estilo, script, - ícone e logo carrega um identificador do próprio conteúdo: assim que o arquivo - muda, o navegador é obrigado a baixar a versão nova — e enquanto ele não muda, - segue usando o que já tem (as páginas continuam abrindo rápido). Não é preciso - fazer nada: nem Ctrl+F5, nem limpar cache, nem aba anônima. +- **After updating, the new feature did not show up until the browser cache was cleared.** + The browser kept using the JavaScript and the CSS of the previous version, which it + had already stored — the screen opened with the old behaviour and nothing indicated + that something was out of date. Anyone testing a freshly published change concluded, + rightly, that it had not worked. Now every stylesheet, script, icon and logo file + carries an identifier derived from its own content: as soon as the file + changes, the browser is forced to download the new version — and while it does not + change, it keeps using what it already has (pages still open fast). Nothing needs to + be done: no Ctrl+F5, no cache clearing, no incognito tab. ## [1.38.6] — 2026-08-07 ### Fixed -- **"Finalizar Spool" não finalizava nada.** Na tela de edição do rolo, o botão - mostrava a mensagem "Rolo atualizado" e o rolo continuava ativo — ele salvava a - edição em vez de finalizar. A confirmação "Marcar spool como finalizado?" - também nunca aparecia. Causa: o formulário de finalizar estava escrito **dentro** - do formulário de edição, e o HTML não permite formulário aninhado — o navegador - descarta o de dentro e o botão passa a pertencer ao de fora. Finalizar pela - **lista** de rolos sempre funcionou; só a tela de edição estava afetada. -- **"Remover" filamento salvava em vez de remover.** O mesmo defeito atingia o - botão de remoção (visível só para admin) na tela de edição do filamento, também - sem pedir confirmação. +- **"Finish Spool" did not finish anything.** On the spool edit screen, the button + showed the message "Spool updated" and the spool stayed active — it saved the + edit instead of finishing. The confirmation "Mark spool as finished?" + never appeared either. Cause: the finish form was written **inside** + the edit form, and HTML does not allow a nested form — the browser + discards the inner one and the button starts belonging to the outer one. Finishing from + the spool **list** always worked; only the edit screen was affected. +- **"Remove" on a filament saved instead of removing.** The same defect hit the + removal button (visible only to admins) on the filament edit screen, also + without asking for confirmation. ## [1.38.5] — 2026-08-04 ### Added -- **Página Admin → Sobre.** Nova tela em `/admin/about` com a versão instalada, o - propósito do sistema, um link para o código-fonte no GitHub e o e-mail de - suporte. O projeto passa a **declarar formalmente a licença MIT** com um - arquivo `LICENSE` na raiz do repositório — antes o repositório era público - mas não trazia nenhum arquivo de licença. +- **Admin → About page.** New screen at `/admin/about` with the installed version, the + purpose of the system, a link to the source code on GitHub and the support + e-mail. The project now **formally declares the MIT license** with a + `LICENSE` file at the root of the repository — before, the repository was public + but carried no license file at all. ## [1.38.4] — 2026-07-28 ### Added -- **"Ver novidades" mesmo já atualizado.** A tela de Atualizações mostrava as - notas de versão só enquanto havia uma atualização pendente — quem já estava na - última versão via apenas "Sistema atualizado", sem nenhum jeito de reler o que - mudou nela. Agora, estando em dia, um link mostra as notas da própria versão - instalada (ou, se não puder carregá-las, leva para a página de releases no +- **"See what's new" even when already up to date.** The Updates screen showed the + release notes only while there was a pending update — anyone already on the + latest version saw just "System up to date", with no way to reread what + changed in it. Now, when up to date, a link shows the notes of the installed + version itself (or, if they cannot be loaded, leads to the releases page on GitHub). ## [1.38.3] — 2026-07-28 ### Changed -- **Aviso quando o histórico de novidades não pôde ser carregado por completo.** - A tela de Atualizações mostra as notas de TODAS as versões entre a instalada e a - mais recente; se o GitHub estiver indisponível e o card cair para a nota de só a - última release, agora aparece um aviso discreto avisando que o histórico pode - estar incompleto, com um link para ver tudo no GitHub. Sem aviso quando a - atualização é só um patch acima (aí a última nota já é a história inteira). +- **Warning when the history of new features could not be loaded in full.** + The Updates screen shows the notes of ALL versions between the installed one and the + most recent; if GitHub is unavailable and the card falls back to the note of only the + last release, a discreet warning now appears saying that the history may + be incomplete, with a link to see everything on GitHub. No warning when the + update is just one patch ahead (there the last note is already the whole story). ## [1.38.2] — 2026-07-10 ### Security -- **Sanitiza `color_hex` na saída (anti CSS injection).** A cor do filamento é campo - de texto livre e ia direto para `style="…background:…"` e para o `stroke` do SVG. - O autoescape do Jinja impede sair do atributo, mas não impede injetar propriedades - CSS dentro dele. Novo filtro `hexcolor` só deixa passar um `#RGB`/`#RRGGBB` válido - (senão usa um neutro), aplicado em todas as amostras de cor e roscas (lista/detalhe - de rolos e filamentos, busca, fila de etiquetas, inventário). Baixa severidade - (só quem tem escrita define a cor; o CSP já bloqueia carregamento externo), mas é - a correção correta na camada de saída. +- **Sanitizes `color_hex` on output (anti CSS injection).** The filament color is a + free-text field and went straight into `style="…background:…"` and into the SVG `stroke`. + Jinja's autoescape prevents breaking out of the attribute, but does not prevent injecting + CSS properties inside it. A new `hexcolor` filter only lets a valid `#RGB`/`#RRGGBB` + through (otherwise it uses a neutral one), applied to every color swatch and donut + (spool and filament list/detail, search, label queue, inventory). Low severity + (only someone with write access sets the color; the CSP already blocks external + loading), but it is the correct fix at the output layer. ## [1.38.1] — 2026-07-10 ### Changed -- **Coluna "Cor" também na lista de filamentos.** Amostra da cor + nome (informado - ou o balde derivado do hexadecimal), como já havia na lista de rolos. +- **"Color" column in the filament list too.** Color swatch + name (the one entered + or the bucket derived from the hex value), as already existed in the spool list. ## [1.38.0] — 2026-07-10 ### Added -- **Ao cadastrar um filamento, pergunta se quer criar um rolo.** Depois de salvar - um filamento novo, o detalhe abre um modal oferecendo cadastrar um rolo daquele - filamento na hora (leva ao "Novo Spool" com o filamento já selecionado). -- **Coluna "Cor" na lista de rolos.** Cada linha mostra uma amostra da cor + o - nome (informado no filamento ou, na falta, o balde derivado do hexadecimal). +- **When registering a filament, it asks whether you want to create a spool.** After + saving a new filament, the detail screen opens a modal offering to register a spool of + that filament right away (leads to "New Spool" with the filament already selected). +- **"Color" column in the spool list.** Each row shows a swatch of the color + the + name (entered on the filament or, failing that, the bucket derived from the hex value). ### Changed -- **Data de compra já vem preenchida com a data de hoje** no cadastro de rolo - (editável). Vale tanto no cadastro manual quanto vindo do fluxo acima. Na edição - de um rolo existente nada muda. -- **"Ver novidades" agora mostra o histórico acumulado.** Quem está várias versões - atrás vê todas as novidades entre a versão instalada e a última (não só a última - release). Fonte: o `CHANGELOG.md` na tag mais recente (via `raw.githubusercontent`, - sem o limite de 60/h da API); se a busca falhar, cai nas notas da última release. +- **The purchase date now comes pre-filled with today's date** when registering a spool + (editable). Applies both to the manual registration and to coming from the flow above. + When editing an existing spool nothing changes. +- **"See what's new" now shows the accumulated history.** Anyone several versions + behind sees all the news between the installed version and the latest (not just the + last release). Source: the `CHANGELOG.md` at the most recent tag (via + `raw.githubusercontent`, without the API's 60/h limit); if the fetch fails, it falls + back to the notes of the last release. ## [1.37.0] — 2026-07-10 ### Added -- **Excluir rolo (permanente) na lista.** Cada linha da lista de rolos ganhou um - botão de exclusão definitiva (lixeira, vermelho). Diferente de "Finalizar" (que - só marca `active=0` e mantém o histórico), a exclusão apaga o rolo e todo o - histórico de pesagens do banco — irreversível, com confirmação. É **restrita a - admin** (`/spools//delete`, `admin_required`); o botão só aparece para admins. -- **Finalizar direto da lista.** O botão "Finalizar" (que só existia na tela de - edição) agora também está em cada linha da lista, para quem tem permissão de - escrita. -- **Cadastrar vários rolos iguais de uma vez.** A tela de "Novo Spool" ganhou um - campo **Quantidade** (1–50): cria N rolos idênticos com IDs distintos numa só - submissão. Ao cadastrar 1, o fluxo é o de sempre (vai ao detalhe e oferece a - fila). Ao cadastrar vários, volta à lista e oferece adicionar **os N** à fila de - impressão de uma vez (uma etiqueta/QR por rolo), reusando o `label_queue_add_all`. +- **Delete spool (permanent) from the list.** Every row of the spool list gained a + definitive deletion button (trash can, red). Unlike "Finish" (which + only marks `active=0` and keeps the history), deletion erases the spool and its whole + weighing history from the database — irreversible, with confirmation. It is **restricted + to admins** (`/spools//delete`, `admin_required`); the button only shows up for admins. +- **Finish straight from the list.** The "Finish" button (which only existed on the edit + screen) is now also on every row of the list, for anyone with write + permission. +- **Register several identical spools at once.** The "New Spool" screen gained a + **Quantity** field (1–50): it creates N identical spools with distinct IDs in a single + submission. When registering 1, the flow is the usual one (goes to the detail and offers + the queue). When registering several, it goes back to the list and offers to add **all N** + to the print queue at once (one label/QR per spool), reusing `label_queue_add_all`. ## [1.36.2] — 2026-06-11 ### Fixed -- **Card "Ver novidades" some quando a API do GitHub falha (fica só o link).** A - página de atualização busca as notas da release pela REST API do GitHub - (`/releases/latest`), que é fail-open. Numa falha pontual (rate limit de 60/h por - IP — não há token no servidor — ou timeout) `latest_release_notes()` devolvia `''` - e o template caía no link simples do GitHub em vez do card expansível. Agora a - busca **reusa a última nota obtida com sucesso** em vez de zerar (o card só vira - link se **nunca** conseguimos notas) e o timeout subiu de 4s para 8s. +- **The "See what's new" card disappeared when the GitHub API failed (only the link was + left).** The update page fetches the release notes through GitHub's REST API + (`/releases/latest`), which is fail-open. On a one-off failure (rate limit of 60/h per + IP — there is no token on the server — or timeout) `latest_release_notes()` returned `''` + and the template fell back to the plain GitHub link instead of the expandable card. Now + the fetch **reuses the last note obtained successfully** instead of zeroing it out (the + card only turns into a link if we **never** managed to get notes) and the timeout went + from 4s to 8s. ## [1.36.1] — 2026-06-11 -Responsividade no celular. A base já era mobile-first (Bootstrap 5.3, navbar -colapsável, formulários em grid), mas as **listas eram tabelas largas** que rolavam -na horizontal — ruins de ler no telefone — e algumas barras de ação/cabeçalho -estouravam a tela. Só markup/CSS; o desktop não muda. +Responsiveness on mobile. The base was already mobile-first (Bootstrap 5.3, collapsible +navbar, forms on a grid), but the **lists were wide tables** that scrolled +horizontally — bad to read on a phone — and some action/header bars +overflowed the screen. Markup/CSS only; the desktop does not change. ### Changed -- **Listas viram cartões empilhados no celular (≤576px).** Toda tabela de itens - (Spools, Filamentos, Fila de Etiquetas, Estoque Baixo, Relatórios por - Material/Local, Histórico de Pesagens, Carretéis Vazios, Busca, Usuários, Backups, - além dos históricos nos detalhes) agora vira um cartão por linha com `rótulo: valor` - — **sem rolagem horizontal**. No desktop continua tabela. Implementado com um único - bloco `@media (max-width:576px)` sobre a classe `.sc-stack` + `data-label` nas - células; o `` é ocultado no celular. -- **Barras de ferramentas e de ações quebram em linha** (`flex-wrap`) nas páginas de - lista e no detalhe do spool, em vez de espremer/transbordar em telas estreitas. -- **Cartões de login e 2FA** passam de largura fixa (`360px`) para - `max-width:360px` com margem lateral, não colando mais nas bordas em telas de - 320–375px. -- **Toasts e alertas** ganham `max-width:calc(100vw - 2rem)` para não vazar a - largura da viewport no celular. +- **Lists turn into stacked cards on mobile (≤576px).** Every item table + (Spools, Filaments, Label Queue, Low Stock, reports by + Material/Location, Weighing History, Empty Spools, Search, Users, Backups, + plus the histories in the detail screens) now becomes one card per row with + `label: value` — **no horizontal scrolling**. On the desktop it stays a table. + Implemented with a single `@media (max-width:576px)` block over the `.sc-stack` class + + `data-label` on the cells; the `` is hidden on mobile. +- **Toolbars and action bars wrap onto another line** (`flex-wrap`) on the + list pages and on the spool detail, instead of squeezing/overflowing on narrow screens. +- **Login and 2FA cards** go from a fixed width (`360px`) to + `max-width:360px` with a side margin, no longer sticking to the edges on + 320–375px screens. +- **Toasts and alerts** get `max-width:calc(100vw - 2rem)` so they do not overflow the + viewport width on mobile. ## [1.36.0] — 2026-06-10 -Endurecimento de segurança a partir de um teste externo (caixa-preta). Vários -achados do relatório já estavam cobertos pelo código (throttle de login por IP, -mensagem de erro genérica, guarda anti-SSRF e anti open-redirect); o que segue são -as lacunas reais corrigidas, cada uma com teste automatizado. +Security hardening driven by an external (black-box) test. Several +findings in the report were already covered by the code (per-IP login throttle, +generic error message, anti-SSRF and anti open-redirect guards); what follows are +the real gaps that were fixed, each one with an automated test. ### Security -- **Controle de acesso por papel nas rotas de escrita (RBAC).** O papel `viewer` - é rotulado "somente leitura", mas as rotas de mutação de inventário - (`/spools/new` e `/edit`/`/weigh`/`/deactivate`, `/weigh`, `/filaments/*`, - `/spool-models/*`) eram protegidas apenas por `@login_required` — um viewer - conseguia criar/editar/excluir/pesar. Novo decorator `@write_required` enforça o - papel **no servidor** (403 para viewer, GET do formulário e POST). Os botões de - escrita também somem na UI para viewer (`can_write`), como defesa em profundidade. -- **Revogação de sessão server-side (CWE-613).** O cookie de sessão do Flask é - *stateless*: `logout` só apagava o cookie do navegador, então um cookie capturado - seguia válido até expirar (até ~30 dias com "manter conectado"); trocar a senha - também não derrubava sessões antigas. Cada usuário agora tem um `session_token` - no banco, revalidado a cada requisição autenticada; **logout e troca de senha - rotacionam o token**, invalidando na hora qualquer cookie antigo (a troca de senha - derruba as *outras* sessões e mantém a atual). Reset de senha por admin derruba a - sessão do alvo. Cookies anteriores a esta versão caem uma vez e o usuário reloga. -- **Chave de API não trafega mais no DOM (CWE-200).** A página Admin → Integrações - embutia a chave inteira num atributo `data-key` (visível em *view-source*). Agora - o HTML só recebe a versão mascarada; o valor em claro é buscado **sob demanda** - por um endpoint admin (`/admin/integrations//key`, resposta `no-store`) ao - clicar em Revelar/Copiar. -- **Exceções de roteamento não viram mais 500.** O `errorhandler(Exception)` - capturava `HTTPException` e a transformava em 500 — um GET em `/logout` - (POST-only) ou um POST em `/spool-models` (GET-only) retornava 500 em vez de 405. - O handler agora repassa `HTTPException` e há uma página 405 dedicada. -- **`/.well-known/security.txt`** (RFC 9116) com canal de divulgação responsável. - -### Notas -- Achados de severidade baixa do relatório foram avaliados e mantidos como estão - (versão no rodapé / IDs sequenciais — atrás de auth e mitigados pelo RBAC; rotas - admin potentes já exigem `@admin_required`). Brute-force: o throttle por IP - (10 falhas / 15 min) já existia; não foi adicionado *lockout por conta* para não - abrir um DoS contra o usuário `admin` conhecido — o 2FA opt-in é o controle forte. +- **Role-based access control on the write routes (RBAC).** The `viewer` role + is labelled "read-only", but the inventory mutation routes + (`/spools/new` and `/edit`/`/weigh`/`/deactivate`, `/weigh`, `/filaments/*`, + `/spool-models/*`) were protected only by `@login_required` — a viewer + could create/edit/delete/weigh. A new `@write_required` decorator enforces the + role **on the server** (403 for a viewer, both the form GET and the POST). The write + buttons also disappear from the UI for a viewer (`can_write`), as defence in depth. +- **Server-side session revocation (CWE-613).** Flask's session cookie is + *stateless*: `logout` only erased the cookie from the browser, so a captured cookie + stayed valid until it expired (up to ~30 days with "keep me signed in"); changing the + password also did not drop old sessions. Each user now has a `session_token` + in the database, revalidated on every authenticated request; **logout and a password + change rotate the token**, immediately invalidating any old cookie (the password change + drops the *other* sessions and keeps the current one). A password reset by an admin + drops the target's session. Cookies from before this version drop once and the user + logs in again. +- **The API key no longer travels in the DOM (CWE-200).** The Admin → Integrations page + embedded the whole key in a `data-key` attribute (visible in *view-source*). Now + the HTML only receives the masked version; the clear value is fetched **on demand** + by an admin endpoint (`/admin/integrations//key`, `no-store` response) when + clicking Reveal/Copy. +- **Routing exceptions no longer turn into 500s.** The `errorhandler(Exception)` + captured `HTTPException` and turned it into a 500 — a GET on `/logout` + (POST-only) or a POST on `/spool-models` (GET-only) returned 500 instead of 405. + The handler now passes `HTTPException` through and there is a dedicated 405 page. +- **`/.well-known/security.txt`** (RFC 9116) with a responsible disclosure channel. + +### Notes +- Low-severity findings from the report were assessed and kept as they are + (version in the footer / sequential IDs — behind auth and mitigated by the RBAC; + powerful admin routes already require `@admin_required`). Brute force: the per-IP + throttle (10 failures / 15 min) already existed; *per-account lockout* was not added + so as not to open a DoS against the well-known `admin` user — the opt-in 2FA is the + strong control. + +--- + +> **Entries below this line are in Brazilian Portuguese.** Release notes switched to +> English as of v1.36.0. Older entries are historical record and were deliberately left +> untranslated — retranslating them would risk altering what was actually recorded. + +--- ## [1.35.2] — 2026-06-10