Skip to content

A price check can report itself, and the dialog is the disclosure - #35

Merged
JIRPOS merged 3 commits into
masterfrom
feat/report-worker
Aug 11, 2026
Merged

A price check can report itself, and the dialog is the disclosure#35
JIRPOS merged 3 commits into
masterfrom
feat/report-worker

Conversation

@JIRPOS

@JIRPOS JIRPOS commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Release notes

ADDED: Report a Bug on the price-check panel, for a check that read an item wrong — an
unmatched modifier, the wrong base, a search that asked for the wrong thing.

  • It opens a dialog that shows you the whole report before it goes anywhere: the item text the
    game copied, everything this tool made of it, the version line, a box to describe the problem,
    and a screenshot you can attach or not. What is on that screen is what is sent, and nothing is
    sent until you press Send.
  • The report is anonymous. There is no login, no identifier of you or your machine, and nothing
    kept on disk afterwards. Reports go to a small relay of the project's own, described in full in
    PRIVACY.md.
  • The screenshot is a picture of this tool's own window and nothing else — the game behind it is
    not in it and cannot be, and every seller's name in a search result is replaced by its position
    before the picture is taken. The box starts unticked either way.
  • If it is sent you get a report id; if it fails the dialog stays open with the reason, so nothing
    you typed is lost.

CHANGED: Search and Open in browser are now icons at the right of the panel rather than two
labelled buttons, which is what makes room for the third.

Review notes

ADDED: A Report a Bug button on the price-check panel, opening a dialog that shows the whole
payload — the clipboard capture, what this tool made of it, the build line and the screenshot —
before anything is sent.

  • Nothing may reach the relay that the preview does not draw, which is why report::to_json
    writes five keys and a test asserts the count.
  • The payload is frozen when the dialog opens: the updater can swap a bundle in and a search can
    land while the user is still reading, and a preview a frame behind what will be sent says
    nothing.

ADDED: report::describe, the parse dump a mispricing is actually diagnosed from: what was read
off the clipboard, what it resolved to in the bundle, which modifier matched which stat record —
and, the line that earns the rest, which matched none.
ADDED: An optional screenshot, read back from our own framebuffer with glReadPixels between the
draw and the swap.

  • It is this process's own pixels and cannot be anything else, so the game behind the transparent
    parts of the overlay is not in it and no other window can be.
  • Rows are flipped and the colour is un-premultiplied on the way out: ImGui blends onto a
    transparent clear, which is right for a compositor and a shade too dark for every PNG reader.

ADDED: A masked frame between the press and the dialog, which is the frame that gets photographed:
every seller's account name is replaced by its position and no button shows its tooltip.

  • Masking in place would work only for as long as the action bar keeps being drawn before the
    results table, and that failure would be silent and would consist of publishing somebody's name.

ADDED: util/png, an encoder with one IDAT, no interlacing and no ancillary chunks at all — no
timestamp, no text, no gamma. Every row is filtered Sub, which is what fits a 900×1080 panel into
~190 KB against the relay's 5 MB cap.
ADDED: ReportService, off the UI thread and deliberately not behind trade::request — the
shared limiter is GGG's policy and nothing else belongs there. The screenshot reaches the worker
as raw pixels, because encoding and serialising it is most of a second.
ADDED: A monospace face chosen at startup, for the two payload previews. A capture is read down
the line and Fontin has no figure alignment at all.
ADDED: Three Font Awesome glyphs, docs/reporting.md, and tests/report_test.cpp.
ADDED: worker/, the Cloudflare Worker the report is posted to, with its deploy scripts and 22
contract tests of its own — every field fenced, mentions disabled, bidi overrides stripped, only
known fields read, and a screenshot that must decode to a real PNG.

  • The app ships the Worker's public URL and never a credential: the Discord webhook is a
    Cloudflare secret, so reading the URL out of the binary yields a rate-limited endpoint.
  • Each report becomes its own forum post, named by us from the item and the report id, so triage
    is Discord's own open/resolved state rather than a convention.

CHANGED: Search and Open in browser are square glyph buttons against the panel's right edge
instead of two words, so the row is one toolbar and fits beside the listings.
CHANGED: PRIVACY.md no longer says there is no server on the other end — there is now exactly
one, reached only by a press, and a section says what a report carries and what it cannot.
CHANGED: zlib becomes ppc_core's one link dependency besides nlohmann/json, and a CI package.

🤖 Generated with Claude Code

JIRPOS and others added 3 commits August 11, 2026 02:50
ADDED: `worker/`, a Cloudflare Worker relaying a bug report from the app to one Discord channel as
an embed plus a `report.md` written to be pasted into an issue unedited.

- The app ships this Worker's public URL, never a credential: the webhook is a Cloudflare secret,
  so extracting the URL from the bundle yields a rate-limited endpoint and nothing more.
- Nothing in the binary sends a report yet, so `PRIVACY.md` is deliberately unchanged — it is part
  of the app-side change, where the claim that there is no server on the other end stops holding.

ADDED: A payload of item text, parse output, an optional comment and an optional PNG screenshot,
with per-field caps that refuse rather than truncate — half an item is a worse report than none.
ADDED: Mentions disabled with `allowed_mentions`, every user-written string fenced, bidi overrides
and control characters stripped, and only known fields read, so a report cannot ping anyone, link
anywhere, or hijack the message shape.
ADDED: A screenshot must decode to a real PNG and is attached under a filename we choose.
ADDED: Optional per-IP and per-day caps in KV.

- They fail open: losing a real report to a KV blip is worse than letting one extra through.

ADDED: `publish.sh` and `rotate-webhook.sh`, reading a gitignored `worker/.env`, with tracing
suspended across the read so `bash -x` cannot print the credentials.

- That file is the whole access control for deploying, and is why no workflow in this repo or the
  data repo may call these scripts: neither public repo holds an Actions secret, and adding the
  first would change the threat model for every workflow at once.

ADDED: `send-test.sh`, whose `--hostile` mode sends mentions, a masked link, a fence break and a
right-to-left override, and which identifies itself so Cloudflare's edge does not answer 1010.
ADDED: 18 contract tests in `worker/test.mjs`, run with `node --test`.
CHANGED: `.gitignore` keeps `.env`, `.dev.vars`, `node_modules/` and `.wrangler/` out of the repo.
CHANGED: `CLAUDE.md`'s doc table points at `worker/README.md`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CHANGED: The relay posts each report as its own forum thread rather than a message in a text
channel, so triage is Discord's own open/resolved state and its tags instead of a convention.

- A forum rejects a message with no `thread_name` and a text channel rejects one that has it, so
  `DISCORD_FORUM` exists to match whichever the webhook points at.

CHANGED: The thread is named from the item plus the report id, capped at Discord's 100 characters
with the id's room reserved before the name is trimmed.

- The name is built from the item's own text, never from anything the reporter wrote, so a report
  cannot choose what a thread in the server is called; the id keeps two reports about the same
  base from becoming one post title.

CHANGED: A Discord rejection now logs the response body, not just the status — the cause is a
channel/payload mismatch that the status alone never names.
ADDED: Four tests covering the thread name, its cap, its single line, and the text-channel
fallback.
CHANGED: `README.md` says the channel must be created as a forum, since Discord cannot convert one
later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ADDED: A **Report a Bug** button on the price-check panel, opening a dialog that shows the whole
payload — the clipboard capture, what this tool made of it, the build line and the screenshot —
before anything is sent.

- Nothing may reach the relay that the preview does not draw, which is why `report::to_json`
  writes five keys and a test asserts the count.
- The payload is frozen when the dialog opens: the updater can swap a bundle in and a search can
  land while the user is still reading, and a preview a frame behind what will be sent says
  nothing.

ADDED: `report::describe`, the parse dump a mispricing is actually diagnosed from: what was read
off the clipboard, what it resolved to in the bundle, which modifier matched which stat record —
and, the line that earns the rest, which matched none.
ADDED: An optional screenshot, read back from our own framebuffer with `glReadPixels` between the
draw and the swap.

- It is this process's own pixels and cannot be anything else, so the game behind the transparent
  parts of the overlay is not in it and no other window can be.
- Rows are flipped and the colour is un-premultiplied on the way out: ImGui blends onto a
  transparent clear, which is right for a compositor and a shade too dark for every PNG reader.

ADDED: A masked frame between the press and the dialog, which is the frame that gets photographed:
every seller's account name is replaced by its position and no button shows its tooltip.

- Masking in place would work only for as long as the action bar keeps being drawn before the
  results table, and that failure would be silent and would consist of publishing somebody's name.

ADDED: `util/png`, an encoder with one `IDAT`, no interlacing and no ancillary chunks at all — no
timestamp, no text, no gamma, which on a picture leaving a user's machine is a property rather
than a shortcut. Every row is filtered Sub, which is what fits a panel under the relay's cap.
ADDED: `ReportService`, off the UI thread and deliberately not behind `trade::request` — the
shared limiter is GGG's policy and nothing else belongs there.
ADDED: A monospace face chosen at startup, for the two payload previews. A capture is read down
the line and Fontin has no figure alignment at all.
ADDED: Three Font Awesome glyphs, and `report_test.cpp`.
CHANGED: Search and Open in browser are square glyph buttons against the panel's right edge
instead of two words, so the row is one toolbar and fits beside the listings.
CHANGED: `PRIVACY.md` no longer says there is no server on the other end — there is now exactly
one, reached only by a press, and a section says what a report carries and what it cannot.
CHANGED: zlib becomes `ppc_core`'s one link dependency besides nlohmann/json, and a CI package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JIRPOS
JIRPOS merged commit 15f0c54 into master Aug 11, 2026
9 checks passed
@JIRPOS
JIRPOS deleted the feat/report-worker branch August 11, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant