From 8be70e56e63ebabebac72f4f9782c2983ca99f85 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 21 Sep 2026 13:09:47 +0200 Subject: [PATCH 1/2] docs: add the bug reporter extensions reference Documents `` and the two modes behind it, under reference/extensions beside Api, Admin, Cli, Infra and Project, since that is where `BugReporter` is exported from. Two things get more room than the prop table, because both are easy to get wrong. Filing needs the token AND the repository, so a token on its own looks like it should work and silently does not. And `repository` defaults to webiny/webiny-js, so a project that configures nothing sends its users to Webiny's issue composer carrying their own page titles and click timeline. Also covers what the recorder captures, and the two rules that keep customer data out of an issue: field values are never read, and a label is only taken from an interactive element. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-docs/6.x/navigation.tsx | 1 + .../reference/extensions/bug-reporter.ai.txt | 54 ++++++++++ .../6.x/reference/extensions/bug-reporter.mdx | 98 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt create mode 100644 docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index 552afa36e..744aae5a0 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -232,6 +232,7 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { + {/* __REFERENCE_PAGES_START__ */} {/* __SDK_PAGES_START__ */} diff --git a/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt b/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt new file mode 100644 index 000000000..0d2699567 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt @@ -0,0 +1,54 @@ +AI Context: Bug Reporter Extensions Reference (bug-reporter.mdx) + +Source of Information: +1. ~/dev/wby-next3/packages/bug-reporter/src/GitHub.tsx — IBugReporterGitHubProps, the three props +2. ~/dev/wby-next3/packages/bug-reporter/src/api/config/BugReportConfig.ts — defaults, label parsing, canFileDirectly +3. ~/dev/wby-next3/packages/bug-reporter/src/api/github/GitHubIssueGateway.ts — assets branch, label creation +4. ~/dev/wby-next3/packages/bug-reporter/src/admin/recording/ActionRecorder.ts — what is recorded, MAX_EVENTS +5. ~/dev/wby-next3/packages/bug-reporter/src/api/readPayload.ts — MAX_SCREENSHOTS, MAX_DESCRIPTION +6. ~/dev/wby-next3/packages/webiny/src/extensions.ts — where BugReporter is exported from +7. webiny/webiny-js#5736 (feature), #5746 (the webiny/extensions export) + +Key Documentation Decisions: +1. Placed under reference/extensions/ beside Api, Admin, Cli, Infra and Project, because BugReporter + is exported from webiny/extensions alongside them. +2. Unlike the other pages in this group, the namespace it documents is not something a project has + to add to get the feature. The reporter ships enabled through DefaultExtensions, and + BugReporter.GitHub is optional configuration on top. The Overview says so up front, since a + reader arriving from the sidebar would otherwise assume the feature needs registering. +3. DefaultExtensions is deliberately NOT named. It is not documented anywhere else in 6.x, and + introducing the concept here would need a section this page does not want. +4. "Filing requires both token and repository" is called out in bold, not buried in the table. It is + the single most likely thing to get wrong: the props are independently optional, so setting only + the token reads like it should work and silently does not. +5. The default repository gets a warning block rather than a table footnote. With no configuration a + customer's compose URLs point at webiny/webiny-js carrying their page titles and click timeline. + That is surprising enough to interrupt the reader for. +6. "What Gets Recorded" is a table plus one paragraph on the two privacy rules (no field values, + labels from interactive elements only). Both rules exist for the same reason and stating that + reason once is what makes them memorable. + +Understanding: +- Two modes. Compose (no credentials) returns a prefilled issues/new URL the user submits + themselves. Filed (token + repository) has the API create the issue and upload screenshots. +- canFileDirectly requires BOTH token and repository to be set. The repository default applies to + compose mode only. +- A malformed repository throws rather than falling back, which is why the page says the report + fails instead of describing a fallback. +- Screenshots go to a bug-report-assets branch because GitHub's issue API has no attachment + endpoint. That is why the token needs contents write and not just issues write. +- reported-in-app is always appended and not configurable. The labels prop REPLACES the "bug" + default rather than adding to it. + +Known Drift To Watch: +- The JSDoc on IBugReporterGitHubProps still describes token alone as the switch into filed mode, + which predates canFileDirectly requiring the repository too. This page follows BugReportConfig, + which is the behaviour. If that JSDoc is corrected upstream, nothing here changes. + +Related Documents: +- docs/developer-docs/6.x/core-concepts/extensions.mdx — conceptual overview of webiny.config.tsx +- docs/developer-docs/6.x/reference/extensions/admin.mdx — sibling reference page, same shape + +Tone Guidelines: +- Reference: minimal prose, tables for props, one short example +- No shell blocks, per project convention — the token is described, not demonstrated diff --git a/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx b/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx new file mode 100644 index 000000000..9cde50447 --- /dev/null +++ b/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx @@ -0,0 +1,98 @@ +--- +id: q7v2mhx4 +title: Bug Reporter Extensions Reference +description: Reference for all BugReporter.* extensions available in webiny.config.tsx. +--- + +import { Alert } from "@/components/Alert"; + + + +- every `BugReporter.*` extension and the props it accepts +- the difference between compose mode and filed mode +- what the bug reporter records and what it never records + + + +## Overview + +The bug reporter turns a description typed in the Admin app into a GitHub issue, with the environment and a timeline of what the user did beforehand attached. It is enabled in every project and needs no configuration. + +Press `cmd+shift+b`, or run "Report a bug" from the command palette. The user must be signed in. + +With no configuration the reporter runs in **compose mode**. The API writes the report up and returns a prefilled GitHub `issues/new` URL, which opens in a new tab for the user to submit under their own account. Nothing is created on your behalf and no credentials are involved. Screenshots cannot travel in a URL, so the user is asked to paste theirs into the composer. + +`BugReporter.GitHub` switches the reporter to **filed mode**, where the API creates the issue itself and uploads screenshots. + +### BugReporter.GitHub + +Points the bug reporter at a GitHub repository. + +| Prop | Type | Required | Description | +| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------- | +| `token` | `string` | No | Personal access token with write access to issues and contents. Omit to stay in compose mode. | +| `repository` | `string` | No | Target repository as `owner/name`. Defaults to `webiny/webiny-js`. | +| `labels` | `string` | No | Comma separated labels applied to every issue. Defaults to `bug`. | + +Use once. + +```tsx webiny.config.tsx +import React from "react"; +import { BugReporter } from "webiny/extensions"; + +export const Extensions = () => { + return ( + <> + + + ); +}; +``` + +**Filing requires both `token` and `repository`.** A token on its own is not enough, and leaves the reporter in compose mode. Filing is the irreversible direction, so the target has to be named explicitly rather than inherited from a default. + + + +Set `repository` even if you do not want filing. In compose mode it is the repository the prefilled URL points at, and it defaults to `webiny/webiny-js`. A project that configures nothing sends its users to Webiny's issue composer, prefilled with their page titles, URLs and click timeline. + + + +A value that is not exactly `owner/name` fails the report rather than falling back to the default. + +### Token + +A classic personal access token with the `repo` scope covers what filed mode needs. + +Write access to **contents** is required as well as issues, and not optional once anyone pastes a screenshot. GitHub's issue API has no attachment endpoint, so screenshots are committed to a `bug-report-assets` branch in the target repository and linked from the issue body. The branch is created on the first report that carries an image. + +Always pass the token through a build-time environment variable, never as a literal. The value is serialized into the build artifact, so a hard-coded token is a token committed to source control. + +### Labels + +Labels are applied on top of `reported-in-app`, which every issue gets and which cannot be turned off. It exists so reports filed this way can be found as a group. + +An unset `labels` prop means `bug`. Setting it replaces that default rather than adding to it, so `labels={"admin"}` produces `admin` and `reported-in-app`, not `bug` as well. + +In filed mode, `reported-in-app` is created in the target repository if it does not exist. Any other label you name is your own to create. In compose mode GitHub drops labels entirely for a user without push access to the repository. + +## What Gets Recorded + +Recording starts when the Admin app loads and keeps the most recent 150 events in memory. Nothing leaves the browser until a report is submitted. + +| Recorded | Detail | +| ------------------- | -------------------------------------------------------------------- | +| Route changes | Path and query string | +| Clicks | Accessible label and a short selector, for interactive elements only | +| Field edits | The label of the field, never its value | +| GraphQL operations | Operation name, status and duration | +| Failed requests | Anything that returned 4xx or 5xx | +| Console output | `console.error` and `console.warn` | +| Uncaught exceptions | Message and stack | + +Field values are never recorded, and a label is only read from an interactive element. Clicking a table cell records where the click landed, not what the cell contained. Both rules exist because reports get filed from tenants holding real customer data. + +A report carries at most 10 screenshots and 4000 characters of description. From 22d0eb8ba42aa8edb201d12cc1738041948f1336 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Mon, 21 Sep 2026 13:36:28 +0200 Subject: [PATCH 2/2] docs: fold the bug reporter into the Project extensions reference The config component moved from a `BugReporter` export on `webiny/extensions` to `Project.BugReporter` (webiny/webiny-js#5746), so a sibling section next to Id, Telemetry, AutoInstall and FeatureFlags is the obvious home. One prop table beats two that drift, so the standalone page goes. The conceptual material is compressed rather than dropped: compose versus filed is two sentences, and the recorder is a paragraph instead of a seven-row table, which keeps this in proportion to FeatureFlags above it. Adds a second bolded gotcha alongside "filing needs token and repository": environment variables have to be guarded with `|| ""`, because an unset key renders as an object rather than undefined and fails the new params schema mid-build. That one bit the feature's own PR. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-docs/6.x/navigation.tsx | 1 - .../reference/extensions/bug-reporter.ai.txt | 54 ---------- .../6.x/reference/extensions/bug-reporter.mdx | 98 ------------------- .../6.x/reference/extensions/project.ai.txt | 31 ++++++ .../6.x/reference/extensions/project.mdx | 36 +++++++ 5 files changed, 67 insertions(+), 153 deletions(-) delete mode 100644 docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt delete mode 100644 docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx diff --git a/docs/developer-docs/6.x/navigation.tsx b/docs/developer-docs/6.x/navigation.tsx index 744aae5a0..552afa36e 100644 --- a/docs/developer-docs/6.x/navigation.tsx +++ b/docs/developer-docs/6.x/navigation.tsx @@ -232,7 +232,6 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { - {/* __REFERENCE_PAGES_START__ */} {/* __SDK_PAGES_START__ */} diff --git a/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt b/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt deleted file mode 100644 index 0d2699567..000000000 --- a/docs/developer-docs/6.x/reference/extensions/bug-reporter.ai.txt +++ /dev/null @@ -1,54 +0,0 @@ -AI Context: Bug Reporter Extensions Reference (bug-reporter.mdx) - -Source of Information: -1. ~/dev/wby-next3/packages/bug-reporter/src/GitHub.tsx — IBugReporterGitHubProps, the three props -2. ~/dev/wby-next3/packages/bug-reporter/src/api/config/BugReportConfig.ts — defaults, label parsing, canFileDirectly -3. ~/dev/wby-next3/packages/bug-reporter/src/api/github/GitHubIssueGateway.ts — assets branch, label creation -4. ~/dev/wby-next3/packages/bug-reporter/src/admin/recording/ActionRecorder.ts — what is recorded, MAX_EVENTS -5. ~/dev/wby-next3/packages/bug-reporter/src/api/readPayload.ts — MAX_SCREENSHOTS, MAX_DESCRIPTION -6. ~/dev/wby-next3/packages/webiny/src/extensions.ts — where BugReporter is exported from -7. webiny/webiny-js#5736 (feature), #5746 (the webiny/extensions export) - -Key Documentation Decisions: -1. Placed under reference/extensions/ beside Api, Admin, Cli, Infra and Project, because BugReporter - is exported from webiny/extensions alongside them. -2. Unlike the other pages in this group, the namespace it documents is not something a project has - to add to get the feature. The reporter ships enabled through DefaultExtensions, and - BugReporter.GitHub is optional configuration on top. The Overview says so up front, since a - reader arriving from the sidebar would otherwise assume the feature needs registering. -3. DefaultExtensions is deliberately NOT named. It is not documented anywhere else in 6.x, and - introducing the concept here would need a section this page does not want. -4. "Filing requires both token and repository" is called out in bold, not buried in the table. It is - the single most likely thing to get wrong: the props are independently optional, so setting only - the token reads like it should work and silently does not. -5. The default repository gets a warning block rather than a table footnote. With no configuration a - customer's compose URLs point at webiny/webiny-js carrying their page titles and click timeline. - That is surprising enough to interrupt the reader for. -6. "What Gets Recorded" is a table plus one paragraph on the two privacy rules (no field values, - labels from interactive elements only). Both rules exist for the same reason and stating that - reason once is what makes them memorable. - -Understanding: -- Two modes. Compose (no credentials) returns a prefilled issues/new URL the user submits - themselves. Filed (token + repository) has the API create the issue and upload screenshots. -- canFileDirectly requires BOTH token and repository to be set. The repository default applies to - compose mode only. -- A malformed repository throws rather than falling back, which is why the page says the report - fails instead of describing a fallback. -- Screenshots go to a bug-report-assets branch because GitHub's issue API has no attachment - endpoint. That is why the token needs contents write and not just issues write. -- reported-in-app is always appended and not configurable. The labels prop REPLACES the "bug" - default rather than adding to it. - -Known Drift To Watch: -- The JSDoc on IBugReporterGitHubProps still describes token alone as the switch into filed mode, - which predates canFileDirectly requiring the repository too. This page follows BugReportConfig, - which is the behaviour. If that JSDoc is corrected upstream, nothing here changes. - -Related Documents: -- docs/developer-docs/6.x/core-concepts/extensions.mdx — conceptual overview of webiny.config.tsx -- docs/developer-docs/6.x/reference/extensions/admin.mdx — sibling reference page, same shape - -Tone Guidelines: -- Reference: minimal prose, tables for props, one short example -- No shell blocks, per project convention — the token is described, not demonstrated diff --git a/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx b/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx deleted file mode 100644 index 9cde50447..000000000 --- a/docs/developer-docs/6.x/reference/extensions/bug-reporter.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -id: q7v2mhx4 -title: Bug Reporter Extensions Reference -description: Reference for all BugReporter.* extensions available in webiny.config.tsx. ---- - -import { Alert } from "@/components/Alert"; - - - -- every `BugReporter.*` extension and the props it accepts -- the difference between compose mode and filed mode -- what the bug reporter records and what it never records - - - -## Overview - -The bug reporter turns a description typed in the Admin app into a GitHub issue, with the environment and a timeline of what the user did beforehand attached. It is enabled in every project and needs no configuration. - -Press `cmd+shift+b`, or run "Report a bug" from the command palette. The user must be signed in. - -With no configuration the reporter runs in **compose mode**. The API writes the report up and returns a prefilled GitHub `issues/new` URL, which opens in a new tab for the user to submit under their own account. Nothing is created on your behalf and no credentials are involved. Screenshots cannot travel in a URL, so the user is asked to paste theirs into the composer. - -`BugReporter.GitHub` switches the reporter to **filed mode**, where the API creates the issue itself and uploads screenshots. - -### BugReporter.GitHub - -Points the bug reporter at a GitHub repository. - -| Prop | Type | Required | Description | -| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------- | -| `token` | `string` | No | Personal access token with write access to issues and contents. Omit to stay in compose mode. | -| `repository` | `string` | No | Target repository as `owner/name`. Defaults to `webiny/webiny-js`. | -| `labels` | `string` | No | Comma separated labels applied to every issue. Defaults to `bug`. | - -Use once. - -```tsx webiny.config.tsx -import React from "react"; -import { BugReporter } from "webiny/extensions"; - -export const Extensions = () => { - return ( - <> - - - ); -}; -``` - -**Filing requires both `token` and `repository`.** A token on its own is not enough, and leaves the reporter in compose mode. Filing is the irreversible direction, so the target has to be named explicitly rather than inherited from a default. - - - -Set `repository` even if you do not want filing. In compose mode it is the repository the prefilled URL points at, and it defaults to `webiny/webiny-js`. A project that configures nothing sends its users to Webiny's issue composer, prefilled with their page titles, URLs and click timeline. - - - -A value that is not exactly `owner/name` fails the report rather than falling back to the default. - -### Token - -A classic personal access token with the `repo` scope covers what filed mode needs. - -Write access to **contents** is required as well as issues, and not optional once anyone pastes a screenshot. GitHub's issue API has no attachment endpoint, so screenshots are committed to a `bug-report-assets` branch in the target repository and linked from the issue body. The branch is created on the first report that carries an image. - -Always pass the token through a build-time environment variable, never as a literal. The value is serialized into the build artifact, so a hard-coded token is a token committed to source control. - -### Labels - -Labels are applied on top of `reported-in-app`, which every issue gets and which cannot be turned off. It exists so reports filed this way can be found as a group. - -An unset `labels` prop means `bug`. Setting it replaces that default rather than adding to it, so `labels={"admin"}` produces `admin` and `reported-in-app`, not `bug` as well. - -In filed mode, `reported-in-app` is created in the target repository if it does not exist. Any other label you name is your own to create. In compose mode GitHub drops labels entirely for a user without push access to the repository. - -## What Gets Recorded - -Recording starts when the Admin app loads and keeps the most recent 150 events in memory. Nothing leaves the browser until a report is submitted. - -| Recorded | Detail | -| ------------------- | -------------------------------------------------------------------- | -| Route changes | Path and query string | -| Clicks | Accessible label and a short selector, for interactive elements only | -| Field edits | The label of the field, never its value | -| GraphQL operations | Operation name, status and duration | -| Failed requests | Anything that returned 4xx or 5xx | -| Console output | `console.error` and `console.warn` | -| Uncaught exceptions | Message and stack | - -Field values are never recorded, and a label is only read from an interactive element. Clicking a table cell records where the click landed, not what the cell contained. Both rules exist because reports get filed from tenants holding real customer data. - -A report carries at most 10 screenshots and 4000 characters of description. diff --git a/docs/developer-docs/6.x/reference/extensions/project.ai.txt b/docs/developer-docs/6.x/reference/extensions/project.ai.txt index 84895c0b1..b1de075d5 100644 --- a/docs/developer-docs/6.x/reference/extensions/project.ai.txt +++ b/docs/developer-docs/6.x/reference/extensions/project.ai.txt @@ -22,3 +22,34 @@ Related Documents: Tone Guidelines: - Reference: minimal prose, tables are the content + +--- Project.BugReporter (added when the bug reporter shipped in 6.6.0) --- + +Source of Information: +1. ~/dev/wby-next3/packages/project/src/extensions/BugReporter.tsx — the defineExtension and its params schema +2. ~/dev/wby-next3/packages/bug-reporter/src/api/config/BugReportConfig.ts — defaults, label parsing, canFileDirectly +3. ~/dev/wby-next3/packages/bug-reporter/src/admin/recording/ActionRecorder.ts — what is recorded, MAX_EVENTS +4. webiny/webiny-js#5736 (feature), #5746 (moved here from a BugReporter export on webiny/extensions) + +Key Documentation Decisions: +1. Documented here rather than on its own page. It shipped as `` from a separate + `webiny/extensions` export and briefly had a page of its own; moving it into the Project + namespace made a sibling section the obvious home, and one prop table is better than two that + drift. +2. The conceptual material was compressed rather than dropped. Compose vs filed is two sentences, + and the recorder is one paragraph instead of a seven-row table, which keeps this section in + proportion to Project.FeatureFlags above it. +3. Two bolded gotchas, because both are invisible from the types and both fail quietly or + confusingly: + - filing needs token AND repository, since the props are independently optional + - `process.env.X` must be guarded with `|| ""`; an unset key renders as an object, not undefined, + and fails the params schema mid-build. This bit the feature's own PR. +4. The default repository keeps a warning block. With no configuration a customer's compose URLs + point at webiny/webiny-js carrying their page titles and click timeline. + +Understanding: +- The extension does not enable the reporter. DefaultExtensions already does, in compose mode. +- canFileDirectly requires both token and repository; the repository default applies to compose only. +- A malformed repository throws rather than falling back, hence "fails the report". +- Screenshots need contents write because issues have no attachment API. +- `labels` REPLACES the "bug" default; `reported-in-app` is always appended and not configurable. diff --git a/docs/developer-docs/6.x/reference/extensions/project.mdx b/docs/developer-docs/6.x/reference/extensions/project.mdx index 91440079f..6c2f357d0 100644 --- a/docs/developer-docs/6.x/reference/extensions/project.mdx +++ b/docs/developer-docs/6.x/reference/extensions/project.mdx @@ -84,3 +84,39 @@ Enables or disables Webiny Cloud Platform (WCP) licensed features. lexicalGeneration?: boolean; } ``` + +### Project.BugReporter + +Points the bug reporter at a GitHub repository, so the API files issues itself. + +The bug reporter is enabled in every project already, so this extension is not what turns it on. Without it the reporter runs in **compose mode**: `cmd+shift+b` in the Admin app, describe what broke, and the API returns a prefilled `issues/new` URL that the reporter submits under their own account. No credentials are involved. This switches it to **filed mode**, where the API creates the issue and commits screenshots to a `bug-report-assets` branch. + +| Prop | Type | Required | Description | +| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------- | +| `token` | `string` | No | Personal access token with write access to issues and contents. Omit to stay in compose mode. | +| `repository` | `string` | No | Target repository as `owner/name`. Defaults to `webiny/webiny-js`. | +| `labels` | `string` | No | Comma separated labels applied to every issue. Defaults to `bug`. | + +```tsx webiny.config.tsx + +``` + +**Filing requires both `token` and `repository`.** A token on its own is not enough, and leaves the reporter in compose mode. Filing is the irreversible direction, so the target has to be named explicitly rather than inherited from a default. A value that is not exactly `owner/name` fails the report rather than falling back. + +**Guard every environment variable with `|| ""`.** Reading an unset key off `process.env` while the config renders returns an object rather than `undefined`, which fails the string check and stops the build. + + + +Set `repository` even if you do not want filing. In compose mode it is the repository the prefilled URL points at, and it defaults to `webiny/webiny-js`. A project that configures nothing sends its users to Webiny's issue composer, prefilled with their page titles, URLs and click timeline. + + + +A classic personal access token with the `repo` scope covers filed mode. Write access to **contents** is needed as well as issues, because GitHub's issue API has no attachment endpoint, so screenshots are committed to a branch and linked. Pass the token through a build-time environment variable, never as a literal: the value is serialized into the build artifact. + +Labels are applied on top of `reported-in-app`, which every issue gets and which cannot be turned off. Setting `labels` replaces the `bug` default rather than adding to it, so `labels={"admin"}` produces `admin` and `reported-in-app`. + +Alongside the description, each report carries the environment and a timeline of the last 150 recorded actions: route changes, clicks, field edits, GraphQL operations, anything that returned 4xx or 5xx, `console.error` and `console.warn`, and uncaught exceptions. Field values are never recorded, only the label of the field, and a label is only read from an interactive element, so clicking a table cell records where the click landed rather than what the cell contained. Both rules exist because reports get filed from tenants holding real customer data.