@mushi-mushi/core 1.31.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,143 +2,64 @@
2
2
 
3
3
  > **Your AI wrote it. Mushi tells you why it broke.**
4
4
 
5
- Core types, API client, and shared utilities for every Mushi SDK package.
6
-
7
- > **You almost certainly don't need to install this directly.** Run `npx mushi-mushi` and the wizard will pick the right framework SDK ([`@mushi-mushi/react`](https://npmjs.com/package/@mushi-mushi/react), [`@mushi-mushi/vue`](https://npmjs.com/package/@mushi-mushi/vue), [`@mushi-mushi/svelte`](https://npmjs.com/package/@mushi-mushi/svelte), [`@mushi-mushi/angular`](https://npmjs.com/package/@mushi-mushi/angular), [`@mushi-mushi/react-native`](https://npmjs.com/package/@mushi-mushi/react-native), [`@mushi-mushi/capacitor`](https://npmjs.com/package/@mushi-mushi/capacitor), or [`@mushi-mushi/web`](https://npmjs.com/package/@mushi-mushi/web)) which depends on this package.
8
-
9
- ## What's Inside
10
-
11
- - **Types**: `MushiConfig`, `MushiReport`, `MushiEnvironment`, and all shared interfaces
12
- - **API Client**: Fetch-based HTTP client with retry and exponential backoff. Tags every internal request with `X-Mushi-Internal: <kind>` so framework SDKs can filter their own traffic out of network capture and `apiCascade`. Ships HMAC-signed reporter helpers (`getLatestSdkVersion`, `listReporterReports`, `listReporterComments`, `replyToReporterReport`) for the two-way reply pipeline, plus `postDiscoveryEvent` (v2.1) for the passive inventory channel
13
- - **Pre-Filter**: On-device Stage 0 spam/gibberish filter (runs client-side, zero server cost)
14
- - **Offline Queue**: IndexedDB-backed queue with auto-sync on reconnect
15
- - **Environment Capture**: Browser/device snapshot — viewport, user agent (with **User-Agent Client Hints** when supported), connection info, screen + DPR, accessibility prefs (`prefers-reduced-motion`, `prefers-color-scheme`, `prefers-contrast`), online/displayMode/title, and a one-shot **page load timing** read from `PerformanceNavigationTiming` (TTFB, DOMContentLoaded, FCP, LCP)
16
- - **Reporter Token**: Anonymous persistent identity for report attribution
17
- - **Session ID**: Tab-scoped session correlation
18
- - **Rate Limiter**: Token bucket self-throttle to prevent API flooding
19
- - **Breadcrumb Buffer** (1.0+ — `createBreadcrumbBuffer`): 50-entry ring of `{timestamp, category, level, message, data?}` rows; framework SDKs auto-capture route changes, `console.error/warn`, `[data-testid]` clicks, and SDK lifecycle events. Snapshot is attached to every `MushiReport` (server promotes it to a dedicated `reports.breadcrumbs` jsonb column for GIN-indexed filtering)
20
- - **Exception Normaliser** (1.0+ — `normaliseThrown`): turns any thrown value (`Error`, string, plain object, `null`, `undefined`) into a stable `{ name, message, stack?, cause? }` shape with truncated stacks and cyclic-cause guards. Powers `Mushi.captureException()` in `@mushi-mushi/web`
21
-
22
- ## Public types added in 0.7 → 0.11
23
-
24
- | Type | Purpose |
25
- |----------------------------|-----------------------------------------------------------------------------------------------|
26
- | `MushiPreset` | Posture bundles: `'production-calm' \| 'beta-loud' \| 'internal-debug' \| 'manual-only'` plus the tiers `'minimal' \| 'standard' \| 'full'`. See [Config presets](#config-presets--precedence). |
27
- | `MushiWidgetAnchor` | Raw-CSS positioning (`top` / `right` / `bottom` / `left`) for the widget launcher. |
28
- | `MushiPrivacyConfig` | `maskSelectors`, `blockSelectors`, `allowUserRemoveScreenshot` for screenshot redaction. |
29
- | `MushiUrlMatcher` | `string \| RegExp` element used by `capture.ignoreUrls` and `apiCascade.ignoreUrls`. |
30
- | `MushiApiCascadeConfig` | Object form of `proactive.apiCascade` so URL filters can be declared per-host-app. |
31
- | `MushiDiagnosticsResult` | Return shape of `Mushi.diagnose()` (CSP, runtime-config, capture, widget health). |
32
- | `MushiSdkVersionInfo` | Response shape for `getLatestSdkVersion(packageName)`; powers the outdated-banner UI. |
33
- | `MushiTimelineEntry` | `{ ts, kind: 'route' \| 'click' \| 'request' \| 'log' \| 'screen', payload }` repro entries. |
34
- | `MushiReporterReport` | Reporter-facing report row (HMAC-authed) with `unread_count` for the widget badge. |
35
- | `MushiReporterComment` | Reporter-facing comment row (HMAC-authed) tagged `author_kind: 'admin' \| 'reporter'`. |
36
- | `MushiDiscoverInventoryConfig` | Mushi v2.1 — fine-grained controls for `capture.discoverInventory` (`enabled`, `throttleMs`, `routeTemplates`, `userIdSource`, `captureDomSummary`). Pass `true` for defaults. |
37
- | `MushiDiscoveryEventPayload` | Mushi v2.1 — wire shape for `POST /v1/sdk/discovery`. Mirrored server-side by `_shared/schemas.ts::discoveryEventSchema`; route + page title + testids + network paths + query-param **keys only** + sha256 user id hash. |
38
- | `MushiBreadcrumb` | 1.0+ — `{ timestamp, category, level: 'debug' \| 'info' \| 'warning' \| 'error' \| 'critical', message, data? }`. Mirrors the Sentry breadcrumb shape so the admin can interleave Mushi + Sentry breadcrumbs on one timeline. |
39
- | `MushiSentryContext` | 1.0+ — rich Sentry handshake the SDK captures via `@sentry/browser` v7/v8/v9: `eventId`, `replayId`, `traceId`, `spanId`, `transaction`, `release`, `environment`, `user`, `tags`, `breadcrumbs`, `issueUrl`, `mushiReportId` (bidirectional). |
40
- | `MushiCaptureExceptionOptions` | 1.0+ — options for `Mushi.captureException(err, opts)`: `level`, `tags`, `extras`, `category`, `userIntent` overrides for the structured report. |
41
- | `NormalisedException` | 1.0+ — return type of `normaliseThrown(err)` (`{ name, message, stack?, cause? }`); used internally by `captureException` and exposed for adapters that want to ship their own thin wrappers. |
42
- | `MushiBannerLink` | 1.8+ — flat action on the rich banner layout: `{ label, href? }` opens an external URL in a new tab, `{ label, featureRequest: true }` opens the widget in feature-request mode. Consumed via `MushiBannerConfig.links`. |
43
- | `screenshotSensitiveHint` (on `MushiWidgetConfig`) | 1.19+ — privacy caption under the screenshot preview: `true` = localized default, `string` = custom copy, `false` = hide caption (preview + Remove remain). Console-configurable via `GET /v1/sdk/config`. See [`docs/SDK_SCREENSHOT_PREVIEW.md`](../../docs/SDK_SCREENSHOT_PREVIEW.md). |
44
-
45
- Constants: `MUSHI_INTERNAL_HEADER` (`'X-Mushi-Internal'`),
46
- `MUSHI_INTERNAL_INIT_MARKER`, and the `MushiInternalRequestKind` literal union
47
- are re-exported so framework adapters can build their own self-noise filters.
48
-
49
- ## Config presets & precedence
50
-
51
- Pass `preset` to pick a posture bundle instead of hand-assembling
52
- `widget` / `capture` / `proactive` flags. `expandPreset(config)` expands the
53
- preset into those nested objects; **your explicit config always wins** on every
54
- key (the merge is per-sub-object, so overriding one `capture` flag keeps the
55
- rest of the preset's defaults).
56
-
57
- | Field | `minimal` | `standard` | `full` |
58
- |----------------------|----------------|--------------------|------------------|
59
- | Widget | on (`auto`) | on (SDK default) | on (`auto`) |
60
- | `capture.console` | ✅ | default | ✅ |
61
- | `capture.network` | ❌ | default | ✅ |
62
- | `capture.performance`| ❌ | default | ✅ |
63
- | `capture.screenshot` | `on-report` | default | `auto` |
64
- | `capture.replay` | `off` | default | `lite` |
65
- | Proactive triggers | none | default | all on |
66
-
67
- - **`minimal`** — widget + console capture only, screenshot on report; no
68
- network/performance/replay, no proactive nudges. Leanest footprint.
69
- - **`standard`** — today's SDK defaults. Expansion is a **no-op** (the config
70
- is returned untouched); the preset exists so the intent is explicit and
71
- documented.
72
- - **`full`** — every capture channel on (including a self-contained `lite`
73
- session replay) and all proactive triggers. Loudest / most data.
74
-
75
- The four legacy postures — `production-calm`, `beta-loud`, `internal-debug`,
76
- `manual-only` — are still supported and expand the same way.
77
-
78
- ### Precedence
79
-
80
- Highest wins. `resolveEnvConfig()` only ever supplies
81
- `projectId` / `apiKey` / `apiEndpoint`, so it never competes with a preset for
82
- the nested option objects.
83
-
84
- | Priority | Source | Example |
85
- |----------|-------------------------------------|-------------------------------------------|
86
- | 1 (wins) | Explicit config | `capture: { network: true }` |
87
- | 2 | `preset` | `preset: 'minimal'` → `network: false` |
88
- | 3 | Environment variables | `NEXT_PUBLIC_MUSHI_*` / `VITE_MUSHI_*` |
89
- | 4 | SDK defaults | built-in fallbacks |
90
-
91
- ### Sampling & `beforeSend`
92
-
93
- Top-level `MushiConfig` fields (consumed by `@mushi-mushi/web` and framework wrappers):
94
-
95
- | Field | Default | Behavior |
96
- |-------|---------|----------|
97
- | `sampleRate` | `1` | Probabilistic gate for **automatic** error reports (`0`–`1`). User-initiated feedback always sends. |
98
- | `replaySampleRate` | `1` | Probabilistic gate for session replay; decided once at session init. Independent of `sampleRate`. |
99
- | `replaysOnErrorSampleRate` | `0` | Start a replay buffer on automatic errors when session sample missed. Keep `0` unless you explicitly want rrweb. |
100
- | `tunnel` | — | Same-origin ingest base (e.g. `/api/mushi-tunnel`). Wins over `apiEndpoint` so browser calls stay first-party. Pagehide uses `fetch` keepalive, then a `{ mushiBeacon, path, headers, body }` envelope because `sendBeacon` cannot set headers. |
101
- | `ignoreErrors` / `denyUrls` / `allowUrls` | — | Drop **automatic** captures only. Never applied to user widget feedback. |
102
- | `beforeSend` | — | `(report) => report \| null \| Promise<…>` after built-in PII scrub. Return `null` to drop. |
103
- | `beforeSendFeedback` | — | Deprecated feedback-only hook; ignored when `beforeSend` is set. |
104
-
105
- ### Validation
106
-
107
- `validateConfig(config)` runs at init and **fails loud but never throws**: it
108
- `console.error`s on unknown top-level keys (usually a typo) and on an invalid
109
- `preset` value, then continues. Set `MUSHI_SILENT=1` to suppress the warnings.
5
+ The shared types, API client and utilities under every [Mushi Mushi](https://github.com/kensaurus/mushi-mushi) SDK.
110
6
 
111
- ```typescript
112
- import { expandPreset, validateConfig } from '@mushi-mushi/core';
7
+ **You probably don't need to install this directly.** Run `npx mushi-mushi` and the wizard installs the SDK for your framework ([`react`](https://npmjs.com/package/@mushi-mushi/react), [`vue`](https://npmjs.com/package/@mushi-mushi/vue), [`svelte`](https://npmjs.com/package/@mushi-mushi/svelte), [`angular`](https://npmjs.com/package/@mushi-mushi/angular), [`react-native`](https://npmjs.com/package/@mushi-mushi/react-native), [`capacitor`](https://npmjs.com/package/@mushi-mushi/capacitor) or [`web`](https://npmjs.com/package/@mushi-mushi/web)), which depends on this one. Reach for `core` when you build your own adapter or need the types.
113
8
 
114
- validateConfig(config); // warns on typos / bad preset (never throws)
115
- const resolved = expandPreset(config); // preset → nested objects, explicit wins
9
+ ```mermaid
10
+ flowchart LR
11
+ A["Framework SDK<br/>react, vue, web…"] --> B["@mushi-mushi/core<br/>types · API client · queue"] --> C["Mushi API"]
116
12
  ```
117
13
 
118
- ## Usage
14
+ ## What's inside
15
+
16
+ | Piece | What it does |
17
+ | --- | --- |
18
+ | Types | `MushiConfig`, `MushiReport`, `MushiEnvironment` and every shared interface |
19
+ | API client (`createApiClient`) | Fetch with retry and backoff. Tags its own requests with `X-Mushi-Internal` so SDKs never report on Mushi |
20
+ | Pre-filter (`createPreFilter`) | On-device spam and gibberish filter, before anything is sent |
21
+ | Offline queue (`createOfflineQueue`) | IndexedDB queue that syncs on reconnect |
22
+ | Environment (`captureEnvironment`) | Viewport, user agent and client hints, connection, accessibility preferences, page-load timing |
23
+ | Identity | Anonymous reporter token (`getReporterToken`) and tab-scoped session id (`getSessionId`) |
24
+ | Rate limiter (`createRateLimiter`) | Token bucket that stops an SDK flooding the API |
25
+ | Breadcrumbs (`createBreadcrumbBuffer`) | 50-entry ring of recent events, in Sentry's breadcrumb shape |
26
+ | Errors (`normaliseThrown`) | Turns any thrown value into `{ name, message, stack?, cause? }` |
27
+
28
+ ## Presets
29
+
30
+ Pass `preset` instead of setting every `widget`, `capture` and `proactive` flag. Your explicit config always wins, key by key.
31
+
32
+ | | `minimal` | `standard` | `full` |
33
+ | --- | --- | --- | --- |
34
+ | Console capture | on | SDK default | on |
35
+ | Network and performance | off | SDK default | on |
36
+ | Screenshot | on report | SDK default | automatic |
37
+ | Session replay | off | SDK default | `lite` |
38
+ | Proactive prompts | none | SDK default | all on |
39
+
40
+ The postures `production-calm`, `beta-loud`, `internal-debug` and `manual-only` also work. Precedence, highest first: **explicit config → `preset` → env vars (`NEXT_PUBLIC_MUSHI_*`, `VITE_MUSHI_*`) → SDK defaults.**
119
41
 
120
42
  ```typescript
121
- import {
122
- createApiClient,
123
- createPreFilter,
124
- captureEnvironment,
125
- createRateLimiter,
126
- MUSHI_INTERNAL_HEADER,
127
- } from '@mushi-mushi/core';
43
+ import { expandPreset, validateConfig } from '@mushi-mushi/core';
44
+
45
+ validateConfig(config); // warns on typos or a bad preset, never throws (MUSHI_SILENT=1 hides it)
46
+ const resolved = expandPreset(config); // preset → nested options, explicit values win
128
47
  ```
129
48
 
130
- This package is used internally by `@mushi-mushi/web` and `@mushi-mushi/react`. Most consumers should use those packages instead.
49
+ ## Sampling and filters
50
+
51
+ Sampling and URL filters apply to **automatic** error reports only, so a report a user sends from the widget is never sampled or filtered out. Only your own `beforeSend` can change or drop it.
131
52
 
132
- ## Bundle Size
53
+ | Field | Default | What it does |
54
+ | --- | --- | --- |
55
+ | `sampleRate` | `1` | Share of automatic error reports to send (`0`–`1`) |
56
+ | `replaySampleRate` | `1` | Share of sessions that record replay |
57
+ | `ignoreErrors` / `denyUrls` / `allowUrls` | — | Drop matching automatic captures |
58
+ | `beforeSend` | — | `(report) => report \| null` for every report, after the PII scrubber. Return `null` to drop |
59
+ | `tunnel` | — | Same-origin ingest path (e.g. `/api/mushi-tunnel`) so ad blockers don't drop reports |
133
60
 
134
- ~3.15 KB brotli (limit: 15 KB)
61
+ Full option reference: [Core SDK docs](https://kensaur.us/mushi-mushi/docs/sdks/core) · [Presets](https://kensaur.us/mushi-mushi/docs/sdks/presets) · [Runtime config](https://kensaur.us/mushi-mushi/docs/concepts/runtime-config).
135
62
 
136
63
  ## License
137
64
 
138
65
  MIT
139
-
140
-
141
- <!-- mushi-readme-stats-footer -->
142
- ---
143
-
144
- <sub>Monorepo scale (July 2026): 64 edge functions · 410 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
@@ -17,7 +17,7 @@ var REPORTER_CANONICAL_STATUSES = [
17
17
  "verified",
18
18
  "reopened"
19
19
  ];
20
- var REPORTER_LOCALES = ["en", "ja", "es", "th"];
20
+ var REPORTER_LOCALES = ["en", "ja", "es", "th", "zh"];
21
21
  var REPORTER_CATEGORIES = ["bug", "slow", "visual", "confusing", "idea"];
22
22
  var REPORTER_CLOSED_REASONS = [
23
23
  "duplicate",
@@ -432,7 +432,98 @@ var TH = {
432
432
  view: "\u0E14\u0E39"
433
433
  }
434
434
  };
435
- var COPY = { en: EN, ja: JA, es: ES, th: TH };
435
+ var ZH = {
436
+ status: {
437
+ received: "\u5DF2\u6536\u5230",
438
+ reviewing: "\u5904\u7406\u4E2D",
439
+ reviewing_again: "\u91CD\u65B0\u67E5\u770B\u4E2D",
440
+ fixing: "\u4FEE\u590D\u4E2D",
441
+ verified: "\u5DF2\u786E\u8BA4\u4FEE\u590D",
442
+ fixed_next: "\u5DF2\u4FEE\u590D \u2014 \u4E0B\u6B21\u66F4\u65B0\u4E0A\u7EBF",
443
+ fixed_version: "\u5DF2\u5728 v{version} \u4FEE\u590D",
444
+ fixed: "\u5DF2\u4FEE\u590D",
445
+ closed: "\u5DF2\u5173\u95ED",
446
+ waiting: "\u7B49\u5F85\u4F60\u7684\u56DE\u590D"
447
+ },
448
+ detail: {
449
+ received: "\u6211\u4EEC\u5DF2\u6536\u5230\u4F60\u7684\u53CD\u9988\u3002",
450
+ reviewing: "\u5F00\u53D1\u8005\u5DF2\u628A\u5B83\u52A0\u5165\u5F85\u5904\u7406\u5217\u8868\u3002",
451
+ reviewing_again: "\u8C22\u8C22\uFF0C\u6211\u4EEC\u6B63\u5728\u91CD\u65B0\u67E5\u770B\u3002",
452
+ fixing: "\u6B63\u5728\u4FEE\u590D\u3002",
453
+ verified: "\u611F\u8C22\u4F60\u786E\u8BA4\u4FEE\u590D\uFF01",
454
+ fixed_next: "\u4F1A\u5728\u4E0B\u4E00\u4E2A\u7248\u672C\u4E2D\u4E0A\u7EBF\u3002",
455
+ fixed_version: "\u8BF7\u66F4\u65B0\u5230 v{version}\u3002\u95EE\u9898\u89E3\u51B3\u4E86\u5417\uFF1F",
456
+ fixed: "\u95EE\u9898\u89E3\u51B3\u4E86\u5417\uFF1F",
457
+ closed: "\u5DF2\u5173\u95ED\u3002",
458
+ waiting: "\u5F00\u53D1\u8005\u6709\u95EE\u9898\u60F3\u95EE\u4F60\uFF0C\u8BF7\u5728\u4E0B\u65B9\u56DE\u590D\u3002"
459
+ },
460
+ idea: {
461
+ received: "\u8C22\u8C22\u4F60\u7684\u60F3\u6CD5",
462
+ reviewing: "\u8003\u8651\u4E2D",
463
+ fixing: "\u5F00\u53D1\u4E2D",
464
+ shipped: "\u5DF2\u4E0A\u7EBF",
465
+ shippedVersion: "\u5DF2\u5728 v{version} \u4E0A\u7EBF"
466
+ },
467
+ closedReason: {
468
+ duplicate: "\u548C\u4E4B\u524D\u7684\u4E00\u6761\u53CD\u9988\u76F8\u540C\uFF0C\u6211\u4EEC\u4F1A\u5728\u90A3\u6761\u91CC\u901A\u77E5\u8FDB\u5C55\u3002",
469
+ not_reproducible: "\u6211\u4EEC\u6CA1\u80FD\u590D\u73B0\u3002\u5982\u679C\u518D\u6B21\u53D1\u751F\uFF0C\u8BF7\u56DE\u590D\u3002",
470
+ wont_fix: "\u6211\u4EEC\u51B3\u5B9A\u4E0D\u4FEE\u6539\u8FD9\u4E00\u70B9\u3002",
471
+ working_as_intended: "\u8FD9\u662F\u9884\u671F\u7684\u884C\u4E3A\u3002",
472
+ none: "\u5DF2\u5173\u95ED\u3002"
473
+ },
474
+ others: {
475
+ few: "\u5305\u62EC\u4F60\u5728\u5185\uFF0C\u6709\u51E0\u4E2A\u4EBA\u53CD\u9988\u4E86\u8FD9\u4E2A\u95EE\u9898",
476
+ many: "\u5F88\u591A\u4EBA\u53CD\u9988\u4E86\u8FD9\u4E2A\u95EE\u9898"
477
+ },
478
+ timeline: {
479
+ received: "\u4F60\u63D0\u4EA4\u4E86\u53CD\u9988",
480
+ reviewing: "\u5F00\u53D1\u8005\u6B63\u5728\u67E5\u770B",
481
+ duplicate_linked: "\u548C\u5DF2\u6709\u53CD\u9988\u76F8\u540C\uFF0C\u6211\u4EEC\u4F1A\u5728\u90A3\u6761\u91CC\u901A\u77E5\u8FDB\u5C55",
482
+ info_requested: "\u5F00\u53D1\u8005\u7684\u95EE\u9898\uFF1A{text}",
483
+ reporter_comment: "\u4F60",
484
+ fix_started: "\u6B63\u5728\u4FEE\u590D",
485
+ fixed: "\u5DF2\u4FEE\u590D \u2014 \u4E0B\u6B21\u66F4\u65B0\u4E0A\u7EBF",
486
+ released: "\u5DF2\u5728 v{version} \u53D1\u5E03 \u2014 \u8BF7\u66F4\u65B0",
487
+ verified: "\u5DF2\u786E\u8BA4\u4FEE\u590D",
488
+ reopened: "\u5DF2\u91CD\u65B0\u6253\u5F00"
489
+ },
490
+ categories: {
491
+ bug: "\u9519\u8BEF",
492
+ slow: "\u6162",
493
+ visual: "\u663E\u793A\u95EE\u9898",
494
+ confusing: "\u96BE\u4EE5\u7406\u89E3",
495
+ idea: "\u60F3\u6CD5"
496
+ },
497
+ ui: {
498
+ yes: "\u662F",
499
+ notYet: "\u8FD8\u6CA1\u6709",
500
+ empty: "\u8FD8\u6CA1\u6709\u53CD\u9988\u3002\u63D0\u4EA4\u540E\u4F1A\u5728\u8FD9\u91CC\u663E\u793A\u8FDB\u5C55\u3002",
501
+ loadError: "\u65E0\u6CD5\u52A0\u8F7D\u66F4\u65B0",
502
+ retry: "\u91CD\u8BD5",
503
+ sending: "\u53D1\u9001\u4E2D\u2026",
504
+ sent: "\u5DF2\u53D1\u9001",
505
+ sendFailed: "\u53D1\u9001\u5931\u8D25",
506
+ noReplies: "\u5F00\u53D1\u8005\u8FD8\u6CA1\u6709\u56DE\u590D\u3002",
507
+ you: "\u4F60",
508
+ developer: "\u5F00\u53D1\u8005",
509
+ addWords: "\u8BF7\u518D\u591A\u5199\u51E0\u4E2A\u5B57",
510
+ developerReplied: "\u5F00\u53D1\u8005\u56DE\u590D\uFF1A\u201C{text}\u201D",
511
+ updates: "\u4F60\u7684\u53CD\u9988\u6709 {n} \u6761\u66F4\u65B0",
512
+ receipt: "\u5DF2\u53D1\u9001\u3002\u6709\u8FDB\u5C55\u65F6\u4F1A\u5728\u8FD9\u91CC\u901A\u77E5\u4F60\u3002",
513
+ trackIt: "\u67E5\u770B\u8FDB\u5C55",
514
+ done: "\u5B8C\u6210",
515
+ emailOptIn: "\u901A\u8FC7\u90AE\u4EF6\u63A5\u6536\u66F4\u65B0",
516
+ emailPlaceholder: "you@example.com",
517
+ emailSubmit: "\u7528\u90AE\u4EF6\u63A5\u6536",
518
+ emailCheckInbox: "\u8BF7\u70B9\u51FB\u6536\u4EF6\u7BB1\u4E2D\u90AE\u4EF6\u91CC\u7684\u94FE\u63A5\u5B8C\u6210\u786E\u8BA4\u3002",
519
+ emailInvalid: "\u90AE\u7BB1\u5730\u5740\u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
520
+ emailFailed: "\u65E0\u6CD5\u8BBE\u7F6E\u90AE\u4EF6\u901A\u77E5\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
521
+ toastReplied: "\u5F00\u53D1\u8005\u56DE\u590D\u4E86\u4F60\u7684\u53CD\u9988",
522
+ toastFixed: "\u4F60\u53CD\u9988\u7684\u95EE\u9898\u5DF2\u4FEE\u590D",
523
+ view: "\u67E5\u770B"
524
+ }
525
+ };
526
+ var COPY = { en: EN, ja: JA, es: ES, th: TH, zh: ZH };
436
527
  function resolveReporterLocale(tag) {
437
528
  const base = (tag ?? "").toLowerCase().split(/[-_]/)[0];
438
529
  return REPORTER_LOCALES.includes(base) ? base : "en";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reporter-ui.ts"],"names":[],"mappings":"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","file":"reporter-ui.cjs","sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * FILE: reporter-ui.ts\n * PURPOSE: The one reporter-facing vocabulary shared by every SDK widget\n * (web, React Native, Capacitor): report categories, the end-user\n * status table, the timeline kinds and their copy in en/ja/es/th.\n *\n * OVERVIEW:\n * - `reporterStatus(report, locale)` maps all 14 internal `reports.status`\n * values (plus the `awaiting_reporter` and duplicate-group overlays and the\n * feature-request variants) onto eight end-user states. There is no branch\n * that echoes the raw status: an unknown value reads as \"Received\".\n * - `reporterTimelineText(kind, params, locale)` renders pipeline events from\n * a fixed template per kind. Stored notification payload text is never\n * shown, so internal category / severity can not leak to a reporter.\n *\n * USAGE:\n * - `import { reporterStatus } from '@mushi-mushi/core/reporter-ui'`\n * - Shipped as a subpath so the main `@mushi-mushi/core` entry stays within\n * its size budget; nothing here is re-exported from `index.ts`.\n *\n * Plan 018 (docs/execplans/reporter-loop-v2.md) §2.1 and §2.3.\n */\n\n/** Every value `reports_status_check` allows. Pinned to the server list by a test. */\nexport const REPORTER_CANONICAL_STATUSES = [\n 'new',\n 'pending',\n 'submitted',\n 'queued',\n 'classified',\n 'grouped',\n 'fixing',\n 'fixed',\n 'dismissed',\n 'triaged',\n 'in_progress',\n 'resolved',\n 'verified',\n 'reopened',\n] as const;\n\nexport type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];\n\nexport const REPORTER_LOCALES = ['en', 'ja', 'es', 'th'] as const;\nexport type ReporterLocale = (typeof REPORTER_LOCALES)[number];\n\n/** Optional type chips on the one-screen report form, in display order. */\nexport const REPORTER_CATEGORIES = ['bug', 'slow', 'visual', 'confusing', 'idea'] as const;\nexport type ReporterCategory = (typeof REPORTER_CATEGORIES)[number];\n\n/** `closed_reason` values the `reports_closed_reason_check` constraint allows. */\nexport const REPORTER_CLOSED_REASONS = [\n 'duplicate',\n 'not_reproducible',\n 'wont_fix',\n 'working_as_intended',\n 'spam',\n] as const;\nexport type ReporterClosedReason = (typeof REPORTER_CLOSED_REASONS)[number];\n\nexport type ReporterTone =\n | 'neutral'\n | 'info'\n | 'attention'\n | 'progress'\n | 'success'\n | 'success-muted'\n | 'muted';\n\n/** The end-user state a report is shown in. */\nexport type ReporterStatusKey =\n | 'received'\n | 'reviewing'\n | 'reviewing_again'\n | 'fixing'\n | 'verified'\n | 'fixed_next'\n | 'fixed_version'\n | 'fixed'\n | 'closed'\n | 'waiting';\n\n/** Base state per internal status, before overlays. Exhaustive by type. */\nconst BASE_STATE: Record<ReporterCanonicalStatus, ReporterStatusKey> = {\n new: 'received',\n pending: 'received',\n submitted: 'received',\n queued: 'received',\n classified: 'reviewing',\n triaged: 'reviewing',\n grouped: 'reviewing',\n reopened: 'reviewing_again',\n fixing: 'fixing',\n in_progress: 'fixing',\n verified: 'verified',\n fixed: 'fixed_next',\n resolved: 'fixed',\n dismissed: 'closed',\n};\n\nconst TONE: Record<ReporterStatusKey, ReporterTone> = {\n received: 'neutral',\n reviewing: 'info',\n reviewing_again: 'info',\n fixing: 'progress',\n verified: 'success-muted',\n fixed_next: 'success',\n fixed_version: 'success',\n fixed: 'success',\n closed: 'muted',\n waiting: 'attention',\n};\n\n/** Timeline entry kinds the reporter detail view renders (§2.3). */\nexport const REPORTER_TIMELINE_KINDS = [\n 'received',\n 'reviewing',\n 'duplicate_linked',\n 'info_requested',\n 'comment',\n 'reporter_comment',\n 'fix_started',\n 'fixed',\n 'released',\n 'verified',\n 'reopened',\n 'closed',\n] as const;\nexport type ReporterTimelineKind = (typeof REPORTER_TIMELINE_KINDS)[number];\n\nexport interface ReporterCopy {\n status: Record<ReporterStatusKey, string>;\n detail: Record<ReporterStatusKey, string>;\n idea: { received: string; reviewing: string; fixing: string; shipped: string; shippedVersion: string };\n closedReason: Record<Exclude<ReporterClosedReason, 'spam'>, string> & { none: string };\n others: { few: string; many: string };\n timeline: Record<Exclude<ReporterTimelineKind, 'comment' | 'closed'>, string>;\n categories: Record<ReporterCategory, string>;\n ui: {\n yes: string;\n notYet: string;\n empty: string;\n loadError: string;\n retry: string;\n sending: string;\n sent: string;\n sendFailed: string;\n noReplies: string;\n you: string;\n developer: string;\n addWords: string;\n developerReplied: string;\n updates: string;\n /** Receipt after sending (replaces the form in place). */\n receipt: string;\n trackIt: string;\n done: string;\n /** Email opt-in on the receipt / report detail (never pre-ticked). */\n emailOptIn: string;\n emailPlaceholder: string;\n emailSubmit: string;\n emailCheckInbox: string;\n emailInvalid: string;\n emailFailed: string;\n /** Next-visit toast. */\n toastReplied: string;\n toastFixed: string;\n view: string;\n };\n}\n\nconst EN: ReporterCopy = {\n status: {\n received: 'Received',\n reviewing: 'Looking into it',\n reviewing_again: 'Looking into it again',\n fixing: 'Fix in progress',\n verified: 'Confirmed fixed',\n fixed_next: 'Fixed — coming in the next update',\n fixed_version: 'Fixed in v{version}',\n fixed: 'Fixed',\n closed: 'Closed',\n waiting: 'Waiting on you',\n },\n detail: {\n received: 'We got it.',\n reviewing: 'The developer has it on their list.',\n reviewing_again: 'Thanks — we reopened it.',\n fixing: 'A fix is being worked on.',\n verified: 'You confirmed the fix. Thank you!',\n fixed_next: 'It ships in the next release.',\n fixed_version: 'Update to v{version}. Does it work for you now?',\n fixed: 'Does it work for you now?',\n closed: 'Closed.',\n waiting: 'The developer asked a question — reply below.',\n },\n idea: {\n received: 'Thanks for the idea',\n reviewing: 'Under consideration',\n fixing: 'Being built',\n shipped: 'Shipped',\n shippedVersion: 'Shipped in v{version}',\n },\n closedReason: {\n duplicate: \"Same as an earlier report — we'll update you there.\",\n not_reproducible: \"We couldn't reproduce it. Reply if it happens again.\",\n wont_fix: 'We decided not to change this.',\n working_as_intended: 'This is expected behaviour.',\n none: 'Closed.',\n },\n others: {\n few: 'You and a few others reported this',\n many: 'Many people reported this',\n },\n timeline: {\n received: 'You reported this',\n reviewing: 'The developer is looking into it',\n duplicate_linked: \"Same as an existing report — we'll update you there\",\n info_requested: 'The developer asked: {text}',\n reporter_comment: 'You',\n fix_started: 'A fix is in progress',\n fixed: 'Fixed — coming in the next update',\n released: 'Shipped in v{version} — update to get it',\n verified: \"You confirmed it's fixed\",\n reopened: 'Reopened',\n },\n categories: {\n bug: 'Bug',\n slow: 'Slow',\n visual: 'Looks wrong',\n confusing: 'Confusing',\n idea: 'Idea',\n },\n ui: {\n yes: 'Yes',\n notYet: 'Not yet',\n empty: 'Nothing yet — your reports will show up here with updates.',\n loadError: \"Couldn't load updates\",\n retry: 'Retry',\n sending: 'Sending…',\n sent: 'Sent',\n sendFailed: 'Failed',\n noReplies: 'No developer replies yet.',\n you: 'You',\n developer: 'Developer',\n addWords: 'Add a few words',\n developerReplied: 'Developer replied: “{text}”',\n updates: '{n} updates on your reports',\n receipt: \"Sent. We'll let you know here when there's news.\",\n trackIt: 'Track it',\n done: 'Done',\n emailOptIn: 'Get updates by email',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'Email me updates',\n emailCheckInbox: 'Check your inbox and tap the link to confirm.',\n emailInvalid: 'That does not look like an email address.',\n emailFailed: \"Couldn't set up email updates. Try again later.\",\n toastReplied: 'The developer replied to your report',\n toastFixed: 'Your bug is fixed',\n view: 'View',\n },\n};\n\nconst JA: ReporterCopy = {\n status: {\n received: '受け付けました',\n reviewing: '確認中',\n reviewing_again: '再確認中',\n fixing: '修正中',\n verified: '修正を確認済み',\n fixed_next: '修正済み — 次のアップデートで反映',\n fixed_version: 'v{version} で修正済み',\n fixed: '修正済み',\n closed: '終了',\n waiting: 'あなたの返信待ち',\n },\n detail: {\n received: '報告を受け取りました。',\n reviewing: '開発者が対応リストに入れました。',\n reviewing_again: 'ありがとうございます。再度確認しています。',\n fixing: '修正に取り組んでいます。',\n verified: '修正を確認いただきありがとうございます!',\n fixed_next: '次のリリースで反映されます。',\n fixed_version: 'v{version} にアップデートしてください。解決しましたか?',\n fixed: '解決しましたか?',\n closed: '終了しました。',\n waiting: '開発者から質問があります。下から返信してください。',\n },\n idea: {\n received: 'アイデアをありがとうございます',\n reviewing: '検討中',\n fixing: '開発中',\n shipped: 'リリース済み',\n shippedVersion: 'v{version} でリリース済み',\n },\n closedReason: {\n duplicate: '以前の報告と同じ内容です。そちらで進捗をお知らせします。',\n not_reproducible: '再現できませんでした。再発したら返信してください。',\n wont_fix: 'この点は変更しないことにしました。',\n working_as_intended: 'これは想定どおりの動作です。',\n none: '終了しました。',\n },\n others: {\n few: 'あなたを含め数人が報告しています',\n many: '多くの人が報告しています',\n },\n timeline: {\n received: 'あなたが報告しました',\n reviewing: '開発者が確認しています',\n duplicate_linked: '既存の報告と同じ内容です。そちらで進捗をお知らせします',\n info_requested: '開発者からの質問: {text}',\n reporter_comment: 'あなた',\n fix_started: '修正中です',\n fixed: '修正済み — 次のアップデートで反映',\n released: 'v{version} でリリース — アップデートしてください',\n verified: '修正を確認しました',\n reopened: '再オープンしました',\n },\n categories: {\n bug: 'バグ',\n slow: '遅い',\n visual: '表示がおかしい',\n confusing: 'わかりにくい',\n idea: 'アイデア',\n },\n ui: {\n yes: 'はい',\n notYet: 'まだ',\n empty: 'まだありません。報告するとここに進捗が表示されます。',\n loadError: '更新を読み込めませんでした',\n retry: '再試行',\n sending: '送信中…',\n sent: '送信しました',\n sendFailed: '送信失敗',\n noReplies: '開発者からの返信はまだありません。',\n you: 'あなた',\n developer: '開発者',\n addWords: 'もう少し詳しく書いてください',\n developerReplied: '開発者からの返信:「{text}」',\n updates: 'あなたの報告に {n} 件の更新があります',\n receipt: '送信しました。進展があればここでお知らせします。',\n trackIt: '経過を見る',\n done: '閉じる',\n emailOptIn: 'メールで更新を受け取る',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'メールで受け取る',\n emailCheckInbox: '受信トレイのメールのリンクをタップして確認してください。',\n emailInvalid: 'メールアドレスの形式が正しくありません。',\n emailFailed: 'メール通知を設定できませんでした。後でもう一度お試しください。',\n toastReplied: '開発者があなたの報告に返信しました',\n toastFixed: 'あなたが報告した不具合が修正されました',\n view: '見る',\n },\n};\n\nconst ES: ReporterCopy = {\n status: {\n received: 'Recibido',\n reviewing: 'Lo estamos revisando',\n reviewing_again: 'Lo revisamos de nuevo',\n fixing: 'Corrección en curso',\n verified: 'Corrección confirmada',\n fixed_next: 'Corregido — llega en la próxima actualización',\n fixed_version: 'Corregido en v{version}',\n fixed: 'Corregido',\n closed: 'Cerrado',\n waiting: 'Esperando tu respuesta',\n },\n detail: {\n received: 'Lo recibimos.',\n reviewing: 'El desarrollador lo tiene en su lista.',\n reviewing_again: 'Gracias, lo reabrimos.',\n fixing: 'Se está trabajando en una corrección.',\n verified: 'Confirmaste la corrección. ¡Gracias!',\n fixed_next: 'Llega en la próxima versión.',\n fixed_version: 'Actualiza a v{version}. ¿Ya funciona?',\n fixed: '¿Ya funciona?',\n closed: 'Cerrado.',\n waiting: 'El desarrollador hizo una pregunta: responde abajo.',\n },\n idea: {\n received: 'Gracias por la idea',\n reviewing: 'En consideración',\n fixing: 'En desarrollo',\n shipped: 'Publicado',\n shippedVersion: 'Publicado en v{version}',\n },\n closedReason: {\n duplicate: 'Es igual a un reporte anterior; te avisaremos allí.',\n not_reproducible: 'No pudimos reproducirlo. Responde si vuelve a pasar.',\n wont_fix: 'Decidimos no cambiar esto.',\n working_as_intended: 'Este es el comportamiento esperado.',\n none: 'Cerrado.',\n },\n others: {\n few: 'Tú y algunas personas más reportaron esto',\n many: 'Muchas personas reportaron esto',\n },\n timeline: {\n received: 'Reportaste esto',\n reviewing: 'El desarrollador lo está revisando',\n duplicate_linked: 'Es igual a un reporte existente; te avisaremos allí',\n info_requested: 'El desarrollador preguntó: {text}',\n reporter_comment: 'Tú',\n fix_started: 'Hay una corrección en curso',\n fixed: 'Corregido — llega en la próxima actualización',\n released: 'Publicado en v{version}: actualiza para tenerlo',\n verified: 'Confirmaste que está corregido',\n reopened: 'Reabierto',\n },\n categories: {\n bug: 'Error',\n slow: 'Lento',\n visual: 'Se ve mal',\n confusing: 'Confuso',\n idea: 'Idea',\n },\n ui: {\n yes: 'Sí',\n notYet: 'Todavía no',\n empty: 'Nada todavía: tus reportes aparecerán aquí con novedades.',\n loadError: 'No se pudieron cargar las novedades',\n retry: 'Reintentar',\n sending: 'Enviando…',\n sent: 'Enviado',\n sendFailed: 'Error al enviar',\n noReplies: 'El desarrollador aún no ha respondido.',\n you: 'Tú',\n developer: 'Desarrollador',\n addWords: 'Agrega algunas palabras',\n developerReplied: 'El desarrollador respondió: “{text}”',\n updates: '{n} novedades en tus reportes',\n receipt: 'Enviado. Te avisaremos aquí cuando haya novedades.',\n trackIt: 'Seguirlo',\n done: 'Listo',\n emailOptIn: 'Recibir novedades por correo',\n emailPlaceholder: 'tu@ejemplo.com',\n emailSubmit: 'Enviarme novedades',\n emailCheckInbox: 'Revisa tu correo y toca el enlace para confirmar.',\n emailInvalid: 'Eso no parece un correo electrónico.',\n emailFailed: 'No pudimos activar las novedades por correo. Inténtalo más tarde.',\n toastReplied: 'El desarrollador respondió a tu reporte',\n toastFixed: 'Tu error está corregido',\n view: 'Ver',\n },\n};\n\nconst TH: ReporterCopy = {\n status: {\n received: 'ได้รับแล้ว',\n reviewing: 'กำลังตรวจสอบ',\n reviewing_again: 'กำลังตรวจสอบอีกครั้ง',\n fixing: 'กำลังแก้ไข',\n verified: 'ยืนยันว่าแก้แล้ว',\n fixed_next: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n fixed_version: 'แก้แล้วใน v{version}',\n fixed: 'แก้แล้ว',\n closed: 'ปิดแล้ว',\n waiting: 'รอคำตอบจากคุณ',\n },\n detail: {\n received: 'เราได้รับรายงานแล้ว',\n reviewing: 'นักพัฒนาใส่ไว้ในรายการแล้ว',\n reviewing_again: 'ขอบคุณ — เราเปิดเรื่องนี้อีกครั้งแล้ว',\n fixing: 'กำลังดำเนินการแก้ไข',\n verified: 'คุณยืนยันการแก้ไขแล้ว ขอบคุณ!',\n fixed_next: 'จะมาในรุ่นถัดไป',\n fixed_version: 'อัปเดตเป็น v{version} ตอนนี้ใช้ได้หรือยัง?',\n fixed: 'ตอนนี้ใช้ได้หรือยัง?',\n closed: 'ปิดแล้ว',\n waiting: 'นักพัฒนามีคำถาม — ตอบด้านล่าง',\n },\n idea: {\n received: 'ขอบคุณสำหรับไอเดีย',\n reviewing: 'กำลังพิจารณา',\n fixing: 'กำลังสร้าง',\n shipped: 'เปิดใช้แล้ว',\n shippedVersion: 'เปิดใช้แล้วใน v{version}',\n },\n closedReason: {\n duplicate: 'เหมือนกับรายงานก่อนหน้า — เราจะแจ้งความคืบหน้าที่นั่น',\n not_reproducible: 'เราทำให้เกิดซ้ำไม่ได้ ตอบกลับหากเกิดขึ้นอีก',\n wont_fix: 'เราตัดสินใจไม่เปลี่ยนส่วนนี้',\n working_as_intended: 'นี่เป็นการทำงานตามที่ตั้งใจไว้',\n none: 'ปิดแล้ว',\n },\n others: {\n few: 'คุณและอีกไม่กี่คนรายงานเรื่องนี้',\n many: 'มีหลายคนรายงานเรื่องนี้',\n },\n timeline: {\n received: 'คุณรายงานเรื่องนี้',\n reviewing: 'นักพัฒนากำลังตรวจสอบ',\n duplicate_linked: 'เหมือนกับรายงานที่มีอยู่ — เราจะแจ้งความคืบหน้าที่นั่น',\n info_requested: 'นักพัฒนาถามว่า: {text}',\n reporter_comment: 'คุณ',\n fix_started: 'กำลังแก้ไข',\n fixed: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n released: 'เปิดใช้ใน v{version} — อัปเดตเพื่อรับการแก้ไข',\n verified: 'คุณยืนยันว่าแก้แล้ว',\n reopened: 'เปิดอีกครั้ง',\n },\n categories: {\n bug: 'บั๊ก',\n slow: 'ช้า',\n visual: 'แสดงผลผิด',\n confusing: 'สับสน',\n idea: 'ไอเดีย',\n },\n ui: {\n yes: 'ใช่',\n notYet: 'ยังไม่',\n empty: 'ยังไม่มี — รายงานของคุณจะแสดงที่นี่พร้อมความคืบหน้า',\n loadError: 'โหลดความคืบหน้าไม่ได้',\n retry: 'ลองอีกครั้ง',\n sending: 'กำลังส่ง…',\n sent: 'ส่งแล้ว',\n sendFailed: 'ส่งไม่สำเร็จ',\n noReplies: 'ยังไม่มีคำตอบจากนักพัฒนา',\n you: 'คุณ',\n developer: 'นักพัฒนา',\n addWords: 'เพิ่มรายละเอียดอีกเล็กน้อย',\n developerReplied: 'นักพัฒนาตอบว่า: “{text}”',\n updates: 'มีความคืบหน้า {n} รายการในรายงานของคุณ',\n receipt: 'ส่งแล้ว เราจะแจ้งคุณที่นี่เมื่อมีความคืบหน้า',\n trackIt: 'ติดตาม',\n done: 'เสร็จ',\n emailOptIn: 'รับความคืบหน้าทางอีเมล',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'ส่งความคืบหน้าทางอีเมล',\n emailCheckInbox: 'ตรวจสอบกล่องจดหมายแล้วแตะลิงก์เพื่อยืนยัน',\n emailInvalid: 'ดูเหมือนจะไม่ใช่อีเมลที่ถูกต้อง',\n emailFailed: 'ตั้งค่าการแจ้งทางอีเมลไม่สำเร็จ ลองใหม่ภายหลัง',\n toastReplied: 'นักพัฒนาตอบกลับรายงานของคุณแล้ว',\n toastFixed: 'บั๊กที่คุณรายงานได้รับการแก้ไขแล้ว',\n view: 'ดู',\n },\n};\n\nconst COPY: Record<ReporterLocale, ReporterCopy> = { en: EN, ja: JA, es: ES, th: TH };\n\n/** Resolve a BCP-47 tag (`ja-JP`, `es-419`) to a supported locale; English otherwise. */\nexport function resolveReporterLocale(tag: string | null | undefined): ReporterLocale {\n const base = (tag ?? '').toLowerCase().split(/[-_]/)[0];\n return (REPORTER_LOCALES as readonly string[]).includes(base) ? (base as ReporterLocale) : 'en';\n}\n\n/** The full copy table for a locale (unknown tags fall back to English). */\nexport function reporterCopy(locale: string = 'en'): ReporterCopy {\n return COPY[resolveReporterLocale(locale)];\n}\n\n/** Replace `{name}` placeholders; a missing value leaves an empty string. */\nexport function fillReporterTemplate(\n template: string,\n params: Record<string, string | number | null | undefined> = {},\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === null || value === undefined ? '' : String(value);\n });\n}\n\n/** Fields of a reporter-list row `reporterStatus` reads. All but `status` are optional. */\nexport interface ReporterStatusInput {\n status: string;\n /** True (or a timestamp) while the developer waits for the reporter. */\n awaiting_reporter?: boolean | null;\n awaiting_reporter_at?: string | null;\n fixed_in_version?: string | null;\n closed_reason?: string | null;\n /** Bucketed duplicate count; never a raw number. */\n group_bucket?: 'none' | 'few' | 'many' | null;\n /** `user_category`; \"Feature request\" / \"idea\" selects the idea labels. */\n user_category?: string | null;\n}\n\nexport interface ReporterStatusView {\n key: ReporterStatusKey;\n label: string;\n tone: ReporterTone;\n detail: string;\n /** Show the Yes / Not yet verification buttons. */\n canVerify: boolean;\n /** \"You and a few others reported this\", or null. */\n othersNote: string | null;\n /** `closed_reason = 'spam'`: hide the report from the reporter entirely. */\n hidden: boolean;\n}\n\nconst OPEN_KEYS: ReadonlySet<ReporterStatusKey> = new Set([\n 'received',\n 'reviewing',\n 'reviewing_again',\n 'fixing',\n]);\n\nfunction isCanonical(status: string): status is ReporterCanonicalStatus {\n return (REPORTER_CANONICAL_STATUSES as readonly string[]).includes(status);\n}\n\n/** True when the row is a feature request rather than a bug. */\nexport function isReporterIdea(input: Pick<ReporterStatusInput, 'user_category'>): boolean {\n const cat = (input.user_category ?? '').trim().toLowerCase().replace(/[\\s-]+/g, '_');\n // The web widget and the ingest path send 'feature'; older clients sent\n // 'Feature request'. The server's isFeatureRequest() accepts the same set.\n return cat === 'feature' || cat === 'feature_request' || cat === 'idea';\n}\n\n/**\n * The end-user view of a report. Overlays are checked before the base\n * status: \"Waiting on you\" wins for any open report, and the duplicate-group\n * note is added on top of whatever the base state is.\n */\nexport function reporterStatus(input: ReporterStatusInput, locale: string = 'en'): ReporterStatusView {\n const copy = reporterCopy(locale);\n const status = (input.status ?? '').trim();\n // No raw passthrough: anything the table does not know reads as Received.\n let key: ReporterStatusKey = isCanonical(status) ? BASE_STATE[status] : 'received';\n\n const version = input.fixed_in_version?.trim() || null;\n if ((status === 'fixed' || status === 'resolved') && version) key = 'fixed_version';\n\n const awaiting = Boolean(input.awaiting_reporter) || Boolean(input.awaiting_reporter_at);\n if (awaiting && OPEN_KEYS.has(key)) key = 'waiting';\n\n const idea = isReporterIdea(input);\n let label = copy.status[key];\n let detail = copy.detail[key];\n if (idea) {\n if (key === 'received') label = copy.idea.received;\n else if (key === 'reviewing' || key === 'reviewing_again') label = copy.idea.reviewing;\n else if (key === 'fixing') label = copy.idea.fixing;\n else if (key === 'fixed_version') label = copy.idea.shippedVersion;\n else if (key === 'fixed_next' || key === 'fixed') label = copy.idea.shipped;\n }\n\n const reason = input.closed_reason ?? null;\n if (key === 'closed') {\n detail =\n reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n\n const bucket = input.group_bucket ?? 'none';\n const othersNote =\n OPEN_KEYS.has(key) || key === 'waiting'\n ? bucket === 'few'\n ? copy.others.few\n : bucket === 'many'\n ? copy.others.many\n : null\n : null;\n\n return {\n key,\n label: fillReporterTemplate(label, { version }),\n tone: TONE[key],\n detail: fillReporterTemplate(detail, { version }),\n canVerify: key === 'fixed_next' || key === 'fixed_version' || key === 'fixed',\n othersNote,\n hidden: key === 'closed' && reason === 'spam',\n };\n}\n\n/** Parameters a timeline template may interpolate. */\nexport interface ReporterTimelineParams {\n /** Verbatim developer text for `comment` and `info_requested`; the reporter's own for `reporter_comment`. */\n text?: string | null;\n version?: string | null;\n closed_reason?: string | null;\n}\n\n/**\n * Render one timeline entry. Pipeline kinds always come from the template for\n * their kind; only the developer's own words (`comment`, `info_requested`) are\n * passed through, verbatim.\n */\nexport function reporterTimelineText(\n kind: ReporterTimelineKind,\n params: ReporterTimelineParams = {},\n locale: string = 'en',\n): string {\n const copy = reporterCopy(locale);\n if (kind === 'comment') return params.text ?? '';\n if (kind === 'closed') {\n const reason = params.closed_reason ?? '';\n return reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n if (kind === 'reporter_comment') return params.text ?? copy.timeline.reporter_comment;\n return fillReporterTemplate(copy.timeline[kind], { text: params.text, version: params.version });\n}\n\n/** Reporter-facing label for a category chip. */\nexport function reporterCategoryLabel(category: ReporterCategory, locale: string = 'en'): string {\n return reporterCopy(locale).categories[category];\n}\n\n// ---------------------------------------------------------------------------\n// Widget rules shared by web and React Native (Plan 018 §1.1, §2.3, §4.2)\n// ---------------------------------------------------------------------------\n\n/** A few words are enough; an attachment speaks for itself (§1.1). */\nexport const REPORTER_MIN_DESCRIPTION = 8;\n\n/** Characters Send needs: `min` (default 8), or 0 with a screenshot / element attached. */\nexport function reporterRequiredLength(min: number | undefined, hasAttachment: boolean): number {\n if (hasAttachment) return 0;\n return typeof min === 'number' && Number.isFinite(min) && min >= 0 ? Math.floor(min) : REPORTER_MIN_DESCRIPTION;\n}\n\n/** Whether Send is enabled: some text or an attachment, and enough words. */\nexport function reporterCanSend(text: string, hasAttachment: boolean, min?: number): boolean {\n const trimmed = text.trim();\n if (!trimmed && !hasAttachment) return false;\n return trimmed.length >= reporterRequiredLength(min, hasAttachment);\n}\n\n/** Wire category for an optional chip: none → `other` (triage sets the type); Idea → a feature request. */\nexport function reporterChipToReport(\n chip: ReporterCategory | null,\n): { category: 'bug' | 'slow' | 'visual' | 'confusing' | 'other'; userCategory?: string } {\n if (!chip) return { category: 'other' };\n if (chip === 'idea') return { category: 'other', userCategory: 'feature' };\n return { category: chip };\n}\n\n/** One entry of `GET /v1/reporter/reports/:id` → `timeline` (subset the widgets read). */\nexport interface ReporterTimelineEntry {\n kind: string;\n text: string;\n /** Developer- or project-written text: show `text` as is. */\n custom?: boolean;\n body?: string;\n version?: string;\n closed_reason?: string;\n}\n\n/** True for the people talking (developer / reporter), false for pipeline events. */\nexport function isReporterConversation(entry: Pick<ReporterTimelineEntry, 'kind'>): boolean {\n return entry.kind === 'comment' || entry.kind === 'reporter_comment' || entry.kind === 'info_requested';\n}\n\n/** Text for one timeline entry in the reporter's locale; custom wording is shown as is. */\nexport function reporterTimelineEntryText(entry: ReporterTimelineEntry, locale: string = 'en'): string {\n if (entry.custom || !(REPORTER_TIMELINE_KINDS as readonly string[]).includes(entry.kind)) return entry.text;\n return reporterTimelineText(\n entry.kind as ReporterTimelineKind,\n { text: entry.body ?? null, version: entry.version ?? null, closed_reason: entry.closed_reason ?? null },\n locale,\n );\n}\n\nexport const REPORTER_TOAST_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\n/** The next-visit toast: one per session and at most one a day, only with something unread (§4.2). */\nexport function reporterShouldShowToast(input: {\n enabled: boolean;\n unreadTotal: number;\n shownThisSession: boolean;\n lastShownAt: number | null;\n now: number;\n}): boolean {\n if (!input.enabled || input.unreadTotal <= 0 || input.shownThisSession) return false;\n return input.lastShownAt === null || input.now - input.lastShownAt >= REPORTER_TOAST_INTERVAL_MS;\n}\n\n/** \"The developer replied…\", \"Your bug is fixed\", or \"3 updates on your reports\". */\nexport function reporterToastMessage(\n updates: { unread_total: number; latest: Array<{ kind: string | null }> },\n locale: string = 'en',\n): string {\n const ui = reporterCopy(locale).ui;\n const kind = updates.unread_total === 1 ? updates.latest[0]?.kind : null;\n if (kind === 'comment' || kind === 'info_requested') return ui.toastReplied;\n if (kind === 'fixed' || kind === 'released') return ui.toastFixed;\n return ui.updates.replace('{n}', String(updates.unread_total));\n}\n\n/** A typo screen for the email opt-in box; the server validates the address. */\nexport function isPlausibleReporterEmail(raw: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(raw.trim());\n}\n"]}
1
+ {"version":3,"sources":["../src/reporter-ui.ts"],"names":[],"mappings":"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","file":"reporter-ui.cjs","sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * FILE: reporter-ui.ts\n * PURPOSE: The one reporter-facing vocabulary shared by every SDK widget\n * (web, React Native, Capacitor): report categories, the end-user\n * status table, the timeline kinds and their copy in en/ja/es/th.\n *\n * OVERVIEW:\n * - `reporterStatus(report, locale)` maps all 14 internal `reports.status`\n * values (plus the `awaiting_reporter` and duplicate-group overlays and the\n * feature-request variants) onto eight end-user states. There is no branch\n * that echoes the raw status: an unknown value reads as \"Received\".\n * - `reporterTimelineText(kind, params, locale)` renders pipeline events from\n * a fixed template per kind. Stored notification payload text is never\n * shown, so internal category / severity can not leak to a reporter.\n *\n * USAGE:\n * - `import { reporterStatus } from '@mushi-mushi/core/reporter-ui'`\n * - Shipped as a subpath so the main `@mushi-mushi/core` entry stays within\n * its size budget; nothing here is re-exported from `index.ts`.\n *\n * Plan 018 (docs/execplans/reporter-loop-v2.md) §2.1 and §2.3.\n */\n\n/** Every value `reports_status_check` allows. Pinned to the server list by a test. */\nexport const REPORTER_CANONICAL_STATUSES = [\n 'new',\n 'pending',\n 'submitted',\n 'queued',\n 'classified',\n 'grouped',\n 'fixing',\n 'fixed',\n 'dismissed',\n 'triaged',\n 'in_progress',\n 'resolved',\n 'verified',\n 'reopened',\n] as const;\n\nexport type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];\n\nexport const REPORTER_LOCALES = ['en', 'ja', 'es', 'th', 'zh'] as const;\nexport type ReporterLocale = (typeof REPORTER_LOCALES)[number];\n\n/** Optional type chips on the one-screen report form, in display order. */\nexport const REPORTER_CATEGORIES = ['bug', 'slow', 'visual', 'confusing', 'idea'] as const;\nexport type ReporterCategory = (typeof REPORTER_CATEGORIES)[number];\n\n/** `closed_reason` values the `reports_closed_reason_check` constraint allows. */\nexport const REPORTER_CLOSED_REASONS = [\n 'duplicate',\n 'not_reproducible',\n 'wont_fix',\n 'working_as_intended',\n 'spam',\n] as const;\nexport type ReporterClosedReason = (typeof REPORTER_CLOSED_REASONS)[number];\n\nexport type ReporterTone =\n | 'neutral'\n | 'info'\n | 'attention'\n | 'progress'\n | 'success'\n | 'success-muted'\n | 'muted';\n\n/** The end-user state a report is shown in. */\nexport type ReporterStatusKey =\n | 'received'\n | 'reviewing'\n | 'reviewing_again'\n | 'fixing'\n | 'verified'\n | 'fixed_next'\n | 'fixed_version'\n | 'fixed'\n | 'closed'\n | 'waiting';\n\n/** Base state per internal status, before overlays. Exhaustive by type. */\nconst BASE_STATE: Record<ReporterCanonicalStatus, ReporterStatusKey> = {\n new: 'received',\n pending: 'received',\n submitted: 'received',\n queued: 'received',\n classified: 'reviewing',\n triaged: 'reviewing',\n grouped: 'reviewing',\n reopened: 'reviewing_again',\n fixing: 'fixing',\n in_progress: 'fixing',\n verified: 'verified',\n fixed: 'fixed_next',\n resolved: 'fixed',\n dismissed: 'closed',\n};\n\nconst TONE: Record<ReporterStatusKey, ReporterTone> = {\n received: 'neutral',\n reviewing: 'info',\n reviewing_again: 'info',\n fixing: 'progress',\n verified: 'success-muted',\n fixed_next: 'success',\n fixed_version: 'success',\n fixed: 'success',\n closed: 'muted',\n waiting: 'attention',\n};\n\n/** Timeline entry kinds the reporter detail view renders (§2.3). */\nexport const REPORTER_TIMELINE_KINDS = [\n 'received',\n 'reviewing',\n 'duplicate_linked',\n 'info_requested',\n 'comment',\n 'reporter_comment',\n 'fix_started',\n 'fixed',\n 'released',\n 'verified',\n 'reopened',\n 'closed',\n] as const;\nexport type ReporterTimelineKind = (typeof REPORTER_TIMELINE_KINDS)[number];\n\nexport interface ReporterCopy {\n status: Record<ReporterStatusKey, string>;\n detail: Record<ReporterStatusKey, string>;\n idea: { received: string; reviewing: string; fixing: string; shipped: string; shippedVersion: string };\n closedReason: Record<Exclude<ReporterClosedReason, 'spam'>, string> & { none: string };\n others: { few: string; many: string };\n timeline: Record<Exclude<ReporterTimelineKind, 'comment' | 'closed'>, string>;\n categories: Record<ReporterCategory, string>;\n ui: {\n yes: string;\n notYet: string;\n empty: string;\n loadError: string;\n retry: string;\n sending: string;\n sent: string;\n sendFailed: string;\n noReplies: string;\n you: string;\n developer: string;\n addWords: string;\n developerReplied: string;\n updates: string;\n /** Receipt after sending (replaces the form in place). */\n receipt: string;\n trackIt: string;\n done: string;\n /** Email opt-in on the receipt / report detail (never pre-ticked). */\n emailOptIn: string;\n emailPlaceholder: string;\n emailSubmit: string;\n emailCheckInbox: string;\n emailInvalid: string;\n emailFailed: string;\n /** Next-visit toast. */\n toastReplied: string;\n toastFixed: string;\n view: string;\n };\n}\n\nconst EN: ReporterCopy = {\n status: {\n received: 'Received',\n reviewing: 'Looking into it',\n reviewing_again: 'Looking into it again',\n fixing: 'Fix in progress',\n verified: 'Confirmed fixed',\n fixed_next: 'Fixed — coming in the next update',\n fixed_version: 'Fixed in v{version}',\n fixed: 'Fixed',\n closed: 'Closed',\n waiting: 'Waiting on you',\n },\n detail: {\n received: 'We got it.',\n reviewing: 'The developer has it on their list.',\n reviewing_again: 'Thanks — we reopened it.',\n fixing: 'A fix is being worked on.',\n verified: 'You confirmed the fix. Thank you!',\n fixed_next: 'It ships in the next release.',\n fixed_version: 'Update to v{version}. Does it work for you now?',\n fixed: 'Does it work for you now?',\n closed: 'Closed.',\n waiting: 'The developer asked a question — reply below.',\n },\n idea: {\n received: 'Thanks for the idea',\n reviewing: 'Under consideration',\n fixing: 'Being built',\n shipped: 'Shipped',\n shippedVersion: 'Shipped in v{version}',\n },\n closedReason: {\n duplicate: \"Same as an earlier report — we'll update you there.\",\n not_reproducible: \"We couldn't reproduce it. Reply if it happens again.\",\n wont_fix: 'We decided not to change this.',\n working_as_intended: 'This is expected behaviour.',\n none: 'Closed.',\n },\n others: {\n few: 'You and a few others reported this',\n many: 'Many people reported this',\n },\n timeline: {\n received: 'You reported this',\n reviewing: 'The developer is looking into it',\n duplicate_linked: \"Same as an existing report — we'll update you there\",\n info_requested: 'The developer asked: {text}',\n reporter_comment: 'You',\n fix_started: 'A fix is in progress',\n fixed: 'Fixed — coming in the next update',\n released: 'Shipped in v{version} — update to get it',\n verified: \"You confirmed it's fixed\",\n reopened: 'Reopened',\n },\n categories: {\n bug: 'Bug',\n slow: 'Slow',\n visual: 'Looks wrong',\n confusing: 'Confusing',\n idea: 'Idea',\n },\n ui: {\n yes: 'Yes',\n notYet: 'Not yet',\n empty: 'Nothing yet — your reports will show up here with updates.',\n loadError: \"Couldn't load updates\",\n retry: 'Retry',\n sending: 'Sending…',\n sent: 'Sent',\n sendFailed: 'Failed',\n noReplies: 'No developer replies yet.',\n you: 'You',\n developer: 'Developer',\n addWords: 'Add a few words',\n developerReplied: 'Developer replied: “{text}”',\n updates: '{n} updates on your reports',\n receipt: \"Sent. We'll let you know here when there's news.\",\n trackIt: 'Track it',\n done: 'Done',\n emailOptIn: 'Get updates by email',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'Email me updates',\n emailCheckInbox: 'Check your inbox and tap the link to confirm.',\n emailInvalid: 'That does not look like an email address.',\n emailFailed: \"Couldn't set up email updates. Try again later.\",\n toastReplied: 'The developer replied to your report',\n toastFixed: 'Your bug is fixed',\n view: 'View',\n },\n};\n\nconst JA: ReporterCopy = {\n status: {\n received: '受け付けました',\n reviewing: '確認中',\n reviewing_again: '再確認中',\n fixing: '修正中',\n verified: '修正を確認済み',\n fixed_next: '修正済み — 次のアップデートで反映',\n fixed_version: 'v{version} で修正済み',\n fixed: '修正済み',\n closed: '終了',\n waiting: 'あなたの返信待ち',\n },\n detail: {\n received: '報告を受け取りました。',\n reviewing: '開発者が対応リストに入れました。',\n reviewing_again: 'ありがとうございます。再度確認しています。',\n fixing: '修正に取り組んでいます。',\n verified: '修正を確認いただきありがとうございます!',\n fixed_next: '次のリリースで反映されます。',\n fixed_version: 'v{version} にアップデートしてください。解決しましたか?',\n fixed: '解決しましたか?',\n closed: '終了しました。',\n waiting: '開発者から質問があります。下から返信してください。',\n },\n idea: {\n received: 'アイデアをありがとうございます',\n reviewing: '検討中',\n fixing: '開発中',\n shipped: 'リリース済み',\n shippedVersion: 'v{version} でリリース済み',\n },\n closedReason: {\n duplicate: '以前の報告と同じ内容です。そちらで進捗をお知らせします。',\n not_reproducible: '再現できませんでした。再発したら返信してください。',\n wont_fix: 'この点は変更しないことにしました。',\n working_as_intended: 'これは想定どおりの動作です。',\n none: '終了しました。',\n },\n others: {\n few: 'あなたを含め数人が報告しています',\n many: '多くの人が報告しています',\n },\n timeline: {\n received: 'あなたが報告しました',\n reviewing: '開発者が確認しています',\n duplicate_linked: '既存の報告と同じ内容です。そちらで進捗をお知らせします',\n info_requested: '開発者からの質問: {text}',\n reporter_comment: 'あなた',\n fix_started: '修正中です',\n fixed: '修正済み — 次のアップデートで反映',\n released: 'v{version} でリリース — アップデートしてください',\n verified: '修正を確認しました',\n reopened: '再オープンしました',\n },\n categories: {\n bug: 'バグ',\n slow: '遅い',\n visual: '表示がおかしい',\n confusing: 'わかりにくい',\n idea: 'アイデア',\n },\n ui: {\n yes: 'はい',\n notYet: 'まだ',\n empty: 'まだありません。報告するとここに進捗が表示されます。',\n loadError: '更新を読み込めませんでした',\n retry: '再試行',\n sending: '送信中…',\n sent: '送信しました',\n sendFailed: '送信失敗',\n noReplies: '開発者からの返信はまだありません。',\n you: 'あなた',\n developer: '開発者',\n addWords: 'もう少し詳しく書いてください',\n developerReplied: '開発者からの返信:「{text}」',\n updates: 'あなたの報告に {n} 件の更新があります',\n receipt: '送信しました。進展があればここでお知らせします。',\n trackIt: '経過を見る',\n done: '閉じる',\n emailOptIn: 'メールで更新を受け取る',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'メールで受け取る',\n emailCheckInbox: '受信トレイのメールのリンクをタップして確認してください。',\n emailInvalid: 'メールアドレスの形式が正しくありません。',\n emailFailed: 'メール通知を設定できませんでした。後でもう一度お試しください。',\n toastReplied: '開発者があなたの報告に返信しました',\n toastFixed: 'あなたが報告した不具合が修正されました',\n view: '見る',\n },\n};\n\nconst ES: ReporterCopy = {\n status: {\n received: 'Recibido',\n reviewing: 'Lo estamos revisando',\n reviewing_again: 'Lo revisamos de nuevo',\n fixing: 'Corrección en curso',\n verified: 'Corrección confirmada',\n fixed_next: 'Corregido — llega en la próxima actualización',\n fixed_version: 'Corregido en v{version}',\n fixed: 'Corregido',\n closed: 'Cerrado',\n waiting: 'Esperando tu respuesta',\n },\n detail: {\n received: 'Lo recibimos.',\n reviewing: 'El desarrollador lo tiene en su lista.',\n reviewing_again: 'Gracias, lo reabrimos.',\n fixing: 'Se está trabajando en una corrección.',\n verified: 'Confirmaste la corrección. ¡Gracias!',\n fixed_next: 'Llega en la próxima versión.',\n fixed_version: 'Actualiza a v{version}. ¿Ya funciona?',\n fixed: '¿Ya funciona?',\n closed: 'Cerrado.',\n waiting: 'El desarrollador hizo una pregunta: responde abajo.',\n },\n idea: {\n received: 'Gracias por la idea',\n reviewing: 'En consideración',\n fixing: 'En desarrollo',\n shipped: 'Publicado',\n shippedVersion: 'Publicado en v{version}',\n },\n closedReason: {\n duplicate: 'Es igual a un reporte anterior; te avisaremos allí.',\n not_reproducible: 'No pudimos reproducirlo. Responde si vuelve a pasar.',\n wont_fix: 'Decidimos no cambiar esto.',\n working_as_intended: 'Este es el comportamiento esperado.',\n none: 'Cerrado.',\n },\n others: {\n few: 'Tú y algunas personas más reportaron esto',\n many: 'Muchas personas reportaron esto',\n },\n timeline: {\n received: 'Reportaste esto',\n reviewing: 'El desarrollador lo está revisando',\n duplicate_linked: 'Es igual a un reporte existente; te avisaremos allí',\n info_requested: 'El desarrollador preguntó: {text}',\n reporter_comment: 'Tú',\n fix_started: 'Hay una corrección en curso',\n fixed: 'Corregido — llega en la próxima actualización',\n released: 'Publicado en v{version}: actualiza para tenerlo',\n verified: 'Confirmaste que está corregido',\n reopened: 'Reabierto',\n },\n categories: {\n bug: 'Error',\n slow: 'Lento',\n visual: 'Se ve mal',\n confusing: 'Confuso',\n idea: 'Idea',\n },\n ui: {\n yes: 'Sí',\n notYet: 'Todavía no',\n empty: 'Nada todavía: tus reportes aparecerán aquí con novedades.',\n loadError: 'No se pudieron cargar las novedades',\n retry: 'Reintentar',\n sending: 'Enviando…',\n sent: 'Enviado',\n sendFailed: 'Error al enviar',\n noReplies: 'El desarrollador aún no ha respondido.',\n you: 'Tú',\n developer: 'Desarrollador',\n addWords: 'Agrega algunas palabras',\n developerReplied: 'El desarrollador respondió: “{text}”',\n updates: '{n} novedades en tus reportes',\n receipt: 'Enviado. Te avisaremos aquí cuando haya novedades.',\n trackIt: 'Seguirlo',\n done: 'Listo',\n emailOptIn: 'Recibir novedades por correo',\n emailPlaceholder: 'tu@ejemplo.com',\n emailSubmit: 'Enviarme novedades',\n emailCheckInbox: 'Revisa tu correo y toca el enlace para confirmar.',\n emailInvalid: 'Eso no parece un correo electrónico.',\n emailFailed: 'No pudimos activar las novedades por correo. Inténtalo más tarde.',\n toastReplied: 'El desarrollador respondió a tu reporte',\n toastFixed: 'Tu error está corregido',\n view: 'Ver',\n },\n};\n\nconst TH: ReporterCopy = {\n status: {\n received: 'ได้รับแล้ว',\n reviewing: 'กำลังตรวจสอบ',\n reviewing_again: 'กำลังตรวจสอบอีกครั้ง',\n fixing: 'กำลังแก้ไข',\n verified: 'ยืนยันว่าแก้แล้ว',\n fixed_next: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n fixed_version: 'แก้แล้วใน v{version}',\n fixed: 'แก้แล้ว',\n closed: 'ปิดแล้ว',\n waiting: 'รอคำตอบจากคุณ',\n },\n detail: {\n received: 'เราได้รับรายงานแล้ว',\n reviewing: 'นักพัฒนาใส่ไว้ในรายการแล้ว',\n reviewing_again: 'ขอบคุณ — เราเปิดเรื่องนี้อีกครั้งแล้ว',\n fixing: 'กำลังดำเนินการแก้ไข',\n verified: 'คุณยืนยันการแก้ไขแล้ว ขอบคุณ!',\n fixed_next: 'จะมาในรุ่นถัดไป',\n fixed_version: 'อัปเดตเป็น v{version} ตอนนี้ใช้ได้หรือยัง?',\n fixed: 'ตอนนี้ใช้ได้หรือยัง?',\n closed: 'ปิดแล้ว',\n waiting: 'นักพัฒนามีคำถาม — ตอบด้านล่าง',\n },\n idea: {\n received: 'ขอบคุณสำหรับไอเดีย',\n reviewing: 'กำลังพิจารณา',\n fixing: 'กำลังสร้าง',\n shipped: 'เปิดใช้แล้ว',\n shippedVersion: 'เปิดใช้แล้วใน v{version}',\n },\n closedReason: {\n duplicate: 'เหมือนกับรายงานก่อนหน้า — เราจะแจ้งความคืบหน้าที่นั่น',\n not_reproducible: 'เราทำให้เกิดซ้ำไม่ได้ ตอบกลับหากเกิดขึ้นอีก',\n wont_fix: 'เราตัดสินใจไม่เปลี่ยนส่วนนี้',\n working_as_intended: 'นี่เป็นการทำงานตามที่ตั้งใจไว้',\n none: 'ปิดแล้ว',\n },\n others: {\n few: 'คุณและอีกไม่กี่คนรายงานเรื่องนี้',\n many: 'มีหลายคนรายงานเรื่องนี้',\n },\n timeline: {\n received: 'คุณรายงานเรื่องนี้',\n reviewing: 'นักพัฒนากำลังตรวจสอบ',\n duplicate_linked: 'เหมือนกับรายงานที่มีอยู่ — เราจะแจ้งความคืบหน้าที่นั่น',\n info_requested: 'นักพัฒนาถามว่า: {text}',\n reporter_comment: 'คุณ',\n fix_started: 'กำลังแก้ไข',\n fixed: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n released: 'เปิดใช้ใน v{version} — อัปเดตเพื่อรับการแก้ไข',\n verified: 'คุณยืนยันว่าแก้แล้ว',\n reopened: 'เปิดอีกครั้ง',\n },\n categories: {\n bug: 'บั๊ก',\n slow: 'ช้า',\n visual: 'แสดงผลผิด',\n confusing: 'สับสน',\n idea: 'ไอเดีย',\n },\n ui: {\n yes: 'ใช่',\n notYet: 'ยังไม่',\n empty: 'ยังไม่มี — รายงานของคุณจะแสดงที่นี่พร้อมความคืบหน้า',\n loadError: 'โหลดความคืบหน้าไม่ได้',\n retry: 'ลองอีกครั้ง',\n sending: 'กำลังส่ง…',\n sent: 'ส่งแล้ว',\n sendFailed: 'ส่งไม่สำเร็จ',\n noReplies: 'ยังไม่มีคำตอบจากนักพัฒนา',\n you: 'คุณ',\n developer: 'นักพัฒนา',\n addWords: 'เพิ่มรายละเอียดอีกเล็กน้อย',\n developerReplied: 'นักพัฒนาตอบว่า: “{text}”',\n updates: 'มีความคืบหน้า {n} รายการในรายงานของคุณ',\n receipt: 'ส่งแล้ว เราจะแจ้งคุณที่นี่เมื่อมีความคืบหน้า',\n trackIt: 'ติดตาม',\n done: 'เสร็จ',\n emailOptIn: 'รับความคืบหน้าทางอีเมล',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'ส่งความคืบหน้าทางอีเมล',\n emailCheckInbox: 'ตรวจสอบกล่องจดหมายแล้วแตะลิงก์เพื่อยืนยัน',\n emailInvalid: 'ดูเหมือนจะไม่ใช่อีเมลที่ถูกต้อง',\n emailFailed: 'ตั้งค่าการแจ้งทางอีเมลไม่สำเร็จ ลองใหม่ภายหลัง',\n toastReplied: 'นักพัฒนาตอบกลับรายงานของคุณแล้ว',\n toastFixed: 'บั๊กที่คุณรายงานได้รับการแก้ไขแล้ว',\n view: 'ดู',\n },\n};\n\nconst ZH: ReporterCopy = {\n status: {\n received: '已收到',\n reviewing: '处理中',\n reviewing_again: '重新查看中',\n fixing: '修复中',\n verified: '已确认修复',\n fixed_next: '已修复 — 下次更新上线',\n fixed_version: '已在 v{version} 修复',\n fixed: '已修复',\n closed: '已关闭',\n waiting: '等待你的回复',\n },\n detail: {\n received: '我们已收到你的反馈。',\n reviewing: '开发者已把它加入待处理列表。',\n reviewing_again: '谢谢,我们正在重新查看。',\n fixing: '正在修复。',\n verified: '感谢你确认修复!',\n fixed_next: '会在下一个版本中上线。',\n fixed_version: '请更新到 v{version}。问题解决了吗?',\n fixed: '问题解决了吗?',\n closed: '已关闭。',\n waiting: '开发者有问题想问你,请在下方回复。',\n },\n idea: {\n received: '谢谢你的想法',\n reviewing: '考虑中',\n fixing: '开发中',\n shipped: '已上线',\n shippedVersion: '已在 v{version} 上线',\n },\n closedReason: {\n duplicate: '和之前的一条反馈相同,我们会在那条里通知进展。',\n not_reproducible: '我们没能复现。如果再次发生,请回复。',\n wont_fix: '我们决定不修改这一点。',\n working_as_intended: '这是预期的行为。',\n none: '已关闭。',\n },\n others: {\n few: '包括你在内,有几个人反馈了这个问题',\n many: '很多人反馈了这个问题',\n },\n timeline: {\n received: '你提交了反馈',\n reviewing: '开发者正在查看',\n duplicate_linked: '和已有反馈相同,我们会在那条里通知进展',\n info_requested: '开发者的问题:{text}',\n reporter_comment: '你',\n fix_started: '正在修复',\n fixed: '已修复 — 下次更新上线',\n released: '已在 v{version} 发布 — 请更新',\n verified: '已确认修复',\n reopened: '已重新打开',\n },\n categories: {\n bug: '错误',\n slow: '慢',\n visual: '显示问题',\n confusing: '难以理解',\n idea: '想法',\n },\n ui: {\n yes: '是',\n notYet: '还没有',\n empty: '还没有反馈。提交后会在这里显示进展。',\n loadError: '无法加载更新',\n retry: '重试',\n sending: '发送中…',\n sent: '已发送',\n sendFailed: '发送失败',\n noReplies: '开发者还没有回复。',\n you: '你',\n developer: '开发者',\n addWords: '请再多写几个字',\n developerReplied: '开发者回复:“{text}”',\n updates: '你的反馈有 {n} 条更新',\n receipt: '已发送。有进展时会在这里通知你。',\n trackIt: '查看进展',\n done: '完成',\n emailOptIn: '通过邮件接收更新',\n emailPlaceholder: 'you@example.com',\n emailSubmit: '用邮件接收',\n emailCheckInbox: '请点击收件箱中邮件里的链接完成确认。',\n emailInvalid: '邮箱地址格式不正确。',\n emailFailed: '无法设置邮件通知,请稍后再试。',\n toastReplied: '开发者回复了你的反馈',\n toastFixed: '你反馈的问题已修复',\n view: '查看',\n },\n};\n\nconst COPY: Record<ReporterLocale, ReporterCopy> = { en: EN, ja: JA, es: ES, th: TH, zh: ZH };\n\n/** Resolve a BCP-47 tag (`ja-JP`, `es-419`) to a supported locale; English otherwise. */\nexport function resolveReporterLocale(tag: string | null | undefined): ReporterLocale {\n const base = (tag ?? '').toLowerCase().split(/[-_]/)[0];\n return (REPORTER_LOCALES as readonly string[]).includes(base) ? (base as ReporterLocale) : 'en';\n}\n\n/** The full copy table for a locale (unknown tags fall back to English). */\nexport function reporterCopy(locale: string = 'en'): ReporterCopy {\n return COPY[resolveReporterLocale(locale)];\n}\n\n/** Replace `{name}` placeholders; a missing value leaves an empty string. */\nexport function fillReporterTemplate(\n template: string,\n params: Record<string, string | number | null | undefined> = {},\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === null || value === undefined ? '' : String(value);\n });\n}\n\n/** Fields of a reporter-list row `reporterStatus` reads. All but `status` are optional. */\nexport interface ReporterStatusInput {\n status: string;\n /** True (or a timestamp) while the developer waits for the reporter. */\n awaiting_reporter?: boolean | null;\n awaiting_reporter_at?: string | null;\n fixed_in_version?: string | null;\n closed_reason?: string | null;\n /** Bucketed duplicate count; never a raw number. */\n group_bucket?: 'none' | 'few' | 'many' | null;\n /** `user_category`; \"Feature request\" / \"idea\" selects the idea labels. */\n user_category?: string | null;\n}\n\nexport interface ReporterStatusView {\n key: ReporterStatusKey;\n label: string;\n tone: ReporterTone;\n detail: string;\n /** Show the Yes / Not yet verification buttons. */\n canVerify: boolean;\n /** \"You and a few others reported this\", or null. */\n othersNote: string | null;\n /** `closed_reason = 'spam'`: hide the report from the reporter entirely. */\n hidden: boolean;\n}\n\nconst OPEN_KEYS: ReadonlySet<ReporterStatusKey> = new Set([\n 'received',\n 'reviewing',\n 'reviewing_again',\n 'fixing',\n]);\n\nfunction isCanonical(status: string): status is ReporterCanonicalStatus {\n return (REPORTER_CANONICAL_STATUSES as readonly string[]).includes(status);\n}\n\n/** True when the row is a feature request rather than a bug. */\nexport function isReporterIdea(input: Pick<ReporterStatusInput, 'user_category'>): boolean {\n const cat = (input.user_category ?? '').trim().toLowerCase().replace(/[\\s-]+/g, '_');\n // The web widget and the ingest path send 'feature'; older clients sent\n // 'Feature request'. The server's isFeatureRequest() accepts the same set.\n return cat === 'feature' || cat === 'feature_request' || cat === 'idea';\n}\n\n/**\n * The end-user view of a report. Overlays are checked before the base\n * status: \"Waiting on you\" wins for any open report, and the duplicate-group\n * note is added on top of whatever the base state is.\n */\nexport function reporterStatus(input: ReporterStatusInput, locale: string = 'en'): ReporterStatusView {\n const copy = reporterCopy(locale);\n const status = (input.status ?? '').trim();\n // No raw passthrough: anything the table does not know reads as Received.\n let key: ReporterStatusKey = isCanonical(status) ? BASE_STATE[status] : 'received';\n\n const version = input.fixed_in_version?.trim() || null;\n if ((status === 'fixed' || status === 'resolved') && version) key = 'fixed_version';\n\n const awaiting = Boolean(input.awaiting_reporter) || Boolean(input.awaiting_reporter_at);\n if (awaiting && OPEN_KEYS.has(key)) key = 'waiting';\n\n const idea = isReporterIdea(input);\n let label = copy.status[key];\n let detail = copy.detail[key];\n if (idea) {\n if (key === 'received') label = copy.idea.received;\n else if (key === 'reviewing' || key === 'reviewing_again') label = copy.idea.reviewing;\n else if (key === 'fixing') label = copy.idea.fixing;\n else if (key === 'fixed_version') label = copy.idea.shippedVersion;\n else if (key === 'fixed_next' || key === 'fixed') label = copy.idea.shipped;\n }\n\n const reason = input.closed_reason ?? null;\n if (key === 'closed') {\n detail =\n reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n\n const bucket = input.group_bucket ?? 'none';\n const othersNote =\n OPEN_KEYS.has(key) || key === 'waiting'\n ? bucket === 'few'\n ? copy.others.few\n : bucket === 'many'\n ? copy.others.many\n : null\n : null;\n\n return {\n key,\n label: fillReporterTemplate(label, { version }),\n tone: TONE[key],\n detail: fillReporterTemplate(detail, { version }),\n canVerify: key === 'fixed_next' || key === 'fixed_version' || key === 'fixed',\n othersNote,\n hidden: key === 'closed' && reason === 'spam',\n };\n}\n\n/** Parameters a timeline template may interpolate. */\nexport interface ReporterTimelineParams {\n /** Verbatim developer text for `comment` and `info_requested`; the reporter's own for `reporter_comment`. */\n text?: string | null;\n version?: string | null;\n closed_reason?: string | null;\n}\n\n/**\n * Render one timeline entry. Pipeline kinds always come from the template for\n * their kind; only the developer's own words (`comment`, `info_requested`) are\n * passed through, verbatim.\n */\nexport function reporterTimelineText(\n kind: ReporterTimelineKind,\n params: ReporterTimelineParams = {},\n locale: string = 'en',\n): string {\n const copy = reporterCopy(locale);\n if (kind === 'comment') return params.text ?? '';\n if (kind === 'closed') {\n const reason = params.closed_reason ?? '';\n return reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n if (kind === 'reporter_comment') return params.text ?? copy.timeline.reporter_comment;\n return fillReporterTemplate(copy.timeline[kind], { text: params.text, version: params.version });\n}\n\n/** Reporter-facing label for a category chip. */\nexport function reporterCategoryLabel(category: ReporterCategory, locale: string = 'en'): string {\n return reporterCopy(locale).categories[category];\n}\n\n// ---------------------------------------------------------------------------\n// Widget rules shared by web and React Native (Plan 018 §1.1, §2.3, §4.2)\n// ---------------------------------------------------------------------------\n\n/** A few words are enough; an attachment speaks for itself (§1.1). */\nexport const REPORTER_MIN_DESCRIPTION = 8;\n\n/** Characters Send needs: `min` (default 8), or 0 with a screenshot / element attached. */\nexport function reporterRequiredLength(min: number | undefined, hasAttachment: boolean): number {\n if (hasAttachment) return 0;\n return typeof min === 'number' && Number.isFinite(min) && min >= 0 ? Math.floor(min) : REPORTER_MIN_DESCRIPTION;\n}\n\n/** Whether Send is enabled: some text or an attachment, and enough words. */\nexport function reporterCanSend(text: string, hasAttachment: boolean, min?: number): boolean {\n const trimmed = text.trim();\n if (!trimmed && !hasAttachment) return false;\n return trimmed.length >= reporterRequiredLength(min, hasAttachment);\n}\n\n/** Wire category for an optional chip: none → `other` (triage sets the type); Idea → a feature request. */\nexport function reporterChipToReport(\n chip: ReporterCategory | null,\n): { category: 'bug' | 'slow' | 'visual' | 'confusing' | 'other'; userCategory?: string } {\n if (!chip) return { category: 'other' };\n if (chip === 'idea') return { category: 'other', userCategory: 'feature' };\n return { category: chip };\n}\n\n/** One entry of `GET /v1/reporter/reports/:id` → `timeline` (subset the widgets read). */\nexport interface ReporterTimelineEntry {\n kind: string;\n text: string;\n /** Developer- or project-written text: show `text` as is. */\n custom?: boolean;\n body?: string;\n version?: string;\n closed_reason?: string;\n}\n\n/** True for the people talking (developer / reporter), false for pipeline events. */\nexport function isReporterConversation(entry: Pick<ReporterTimelineEntry, 'kind'>): boolean {\n return entry.kind === 'comment' || entry.kind === 'reporter_comment' || entry.kind === 'info_requested';\n}\n\n/** Text for one timeline entry in the reporter's locale; custom wording is shown as is. */\nexport function reporterTimelineEntryText(entry: ReporterTimelineEntry, locale: string = 'en'): string {\n if (entry.custom || !(REPORTER_TIMELINE_KINDS as readonly string[]).includes(entry.kind)) return entry.text;\n return reporterTimelineText(\n entry.kind as ReporterTimelineKind,\n { text: entry.body ?? null, version: entry.version ?? null, closed_reason: entry.closed_reason ?? null },\n locale,\n );\n}\n\nexport const REPORTER_TOAST_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\n/** The next-visit toast: one per session and at most one a day, only with something unread (§4.2). */\nexport function reporterShouldShowToast(input: {\n enabled: boolean;\n unreadTotal: number;\n shownThisSession: boolean;\n lastShownAt: number | null;\n now: number;\n}): boolean {\n if (!input.enabled || input.unreadTotal <= 0 || input.shownThisSession) return false;\n return input.lastShownAt === null || input.now - input.lastShownAt >= REPORTER_TOAST_INTERVAL_MS;\n}\n\n/** \"The developer replied…\", \"Your bug is fixed\", or \"3 updates on your reports\". */\nexport function reporterToastMessage(\n updates: { unread_total: number; latest: Array<{ kind: string | null }> },\n locale: string = 'en',\n): string {\n const ui = reporterCopy(locale).ui;\n const kind = updates.unread_total === 1 ? updates.latest[0]?.kind : null;\n if (kind === 'comment' || kind === 'info_requested') return ui.toastReplied;\n if (kind === 'fixed' || kind === 'released') return ui.toastFixed;\n return ui.updates.replace('{n}', String(updates.unread_total));\n}\n\n/** A typo screen for the email opt-in box; the server validates the address. */\nexport function isPlausibleReporterEmail(raw: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(raw.trim());\n}\n"]}
@@ -23,7 +23,7 @@
23
23
  /** Every value `reports_status_check` allows. Pinned to the server list by a test. */
24
24
  declare const REPORTER_CANONICAL_STATUSES: readonly ["new", "pending", "submitted", "queued", "classified", "grouped", "fixing", "fixed", "dismissed", "triaged", "in_progress", "resolved", "verified", "reopened"];
25
25
  type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];
26
- declare const REPORTER_LOCALES: readonly ["en", "ja", "es", "th"];
26
+ declare const REPORTER_LOCALES: readonly ["en", "ja", "es", "th", "zh"];
27
27
  type ReporterLocale = (typeof REPORTER_LOCALES)[number];
28
28
  /** Optional type chips on the one-screen report form, in display order. */
29
29
  declare const REPORTER_CATEGORIES: readonly ["bug", "slow", "visual", "confusing", "idea"];
@@ -23,7 +23,7 @@
23
23
  /** Every value `reports_status_check` allows. Pinned to the server list by a test. */
24
24
  declare const REPORTER_CANONICAL_STATUSES: readonly ["new", "pending", "submitted", "queued", "classified", "grouped", "fixing", "fixed", "dismissed", "triaged", "in_progress", "resolved", "verified", "reopened"];
25
25
  type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];
26
- declare const REPORTER_LOCALES: readonly ["en", "ja", "es", "th"];
26
+ declare const REPORTER_LOCALES: readonly ["en", "ja", "es", "th", "zh"];
27
27
  type ReporterLocale = (typeof REPORTER_LOCALES)[number];
28
28
  /** Optional type chips on the one-screen report form, in display order. */
29
29
  declare const REPORTER_CATEGORIES: readonly ["bug", "slow", "visual", "confusing", "idea"];
@@ -15,7 +15,7 @@ var REPORTER_CANONICAL_STATUSES = [
15
15
  "verified",
16
16
  "reopened"
17
17
  ];
18
- var REPORTER_LOCALES = ["en", "ja", "es", "th"];
18
+ var REPORTER_LOCALES = ["en", "ja", "es", "th", "zh"];
19
19
  var REPORTER_CATEGORIES = ["bug", "slow", "visual", "confusing", "idea"];
20
20
  var REPORTER_CLOSED_REASONS = [
21
21
  "duplicate",
@@ -430,7 +430,98 @@ var TH = {
430
430
  view: "\u0E14\u0E39"
431
431
  }
432
432
  };
433
- var COPY = { en: EN, ja: JA, es: ES, th: TH };
433
+ var ZH = {
434
+ status: {
435
+ received: "\u5DF2\u6536\u5230",
436
+ reviewing: "\u5904\u7406\u4E2D",
437
+ reviewing_again: "\u91CD\u65B0\u67E5\u770B\u4E2D",
438
+ fixing: "\u4FEE\u590D\u4E2D",
439
+ verified: "\u5DF2\u786E\u8BA4\u4FEE\u590D",
440
+ fixed_next: "\u5DF2\u4FEE\u590D \u2014 \u4E0B\u6B21\u66F4\u65B0\u4E0A\u7EBF",
441
+ fixed_version: "\u5DF2\u5728 v{version} \u4FEE\u590D",
442
+ fixed: "\u5DF2\u4FEE\u590D",
443
+ closed: "\u5DF2\u5173\u95ED",
444
+ waiting: "\u7B49\u5F85\u4F60\u7684\u56DE\u590D"
445
+ },
446
+ detail: {
447
+ received: "\u6211\u4EEC\u5DF2\u6536\u5230\u4F60\u7684\u53CD\u9988\u3002",
448
+ reviewing: "\u5F00\u53D1\u8005\u5DF2\u628A\u5B83\u52A0\u5165\u5F85\u5904\u7406\u5217\u8868\u3002",
449
+ reviewing_again: "\u8C22\u8C22\uFF0C\u6211\u4EEC\u6B63\u5728\u91CD\u65B0\u67E5\u770B\u3002",
450
+ fixing: "\u6B63\u5728\u4FEE\u590D\u3002",
451
+ verified: "\u611F\u8C22\u4F60\u786E\u8BA4\u4FEE\u590D\uFF01",
452
+ fixed_next: "\u4F1A\u5728\u4E0B\u4E00\u4E2A\u7248\u672C\u4E2D\u4E0A\u7EBF\u3002",
453
+ fixed_version: "\u8BF7\u66F4\u65B0\u5230 v{version}\u3002\u95EE\u9898\u89E3\u51B3\u4E86\u5417\uFF1F",
454
+ fixed: "\u95EE\u9898\u89E3\u51B3\u4E86\u5417\uFF1F",
455
+ closed: "\u5DF2\u5173\u95ED\u3002",
456
+ waiting: "\u5F00\u53D1\u8005\u6709\u95EE\u9898\u60F3\u95EE\u4F60\uFF0C\u8BF7\u5728\u4E0B\u65B9\u56DE\u590D\u3002"
457
+ },
458
+ idea: {
459
+ received: "\u8C22\u8C22\u4F60\u7684\u60F3\u6CD5",
460
+ reviewing: "\u8003\u8651\u4E2D",
461
+ fixing: "\u5F00\u53D1\u4E2D",
462
+ shipped: "\u5DF2\u4E0A\u7EBF",
463
+ shippedVersion: "\u5DF2\u5728 v{version} \u4E0A\u7EBF"
464
+ },
465
+ closedReason: {
466
+ duplicate: "\u548C\u4E4B\u524D\u7684\u4E00\u6761\u53CD\u9988\u76F8\u540C\uFF0C\u6211\u4EEC\u4F1A\u5728\u90A3\u6761\u91CC\u901A\u77E5\u8FDB\u5C55\u3002",
467
+ not_reproducible: "\u6211\u4EEC\u6CA1\u80FD\u590D\u73B0\u3002\u5982\u679C\u518D\u6B21\u53D1\u751F\uFF0C\u8BF7\u56DE\u590D\u3002",
468
+ wont_fix: "\u6211\u4EEC\u51B3\u5B9A\u4E0D\u4FEE\u6539\u8FD9\u4E00\u70B9\u3002",
469
+ working_as_intended: "\u8FD9\u662F\u9884\u671F\u7684\u884C\u4E3A\u3002",
470
+ none: "\u5DF2\u5173\u95ED\u3002"
471
+ },
472
+ others: {
473
+ few: "\u5305\u62EC\u4F60\u5728\u5185\uFF0C\u6709\u51E0\u4E2A\u4EBA\u53CD\u9988\u4E86\u8FD9\u4E2A\u95EE\u9898",
474
+ many: "\u5F88\u591A\u4EBA\u53CD\u9988\u4E86\u8FD9\u4E2A\u95EE\u9898"
475
+ },
476
+ timeline: {
477
+ received: "\u4F60\u63D0\u4EA4\u4E86\u53CD\u9988",
478
+ reviewing: "\u5F00\u53D1\u8005\u6B63\u5728\u67E5\u770B",
479
+ duplicate_linked: "\u548C\u5DF2\u6709\u53CD\u9988\u76F8\u540C\uFF0C\u6211\u4EEC\u4F1A\u5728\u90A3\u6761\u91CC\u901A\u77E5\u8FDB\u5C55",
480
+ info_requested: "\u5F00\u53D1\u8005\u7684\u95EE\u9898\uFF1A{text}",
481
+ reporter_comment: "\u4F60",
482
+ fix_started: "\u6B63\u5728\u4FEE\u590D",
483
+ fixed: "\u5DF2\u4FEE\u590D \u2014 \u4E0B\u6B21\u66F4\u65B0\u4E0A\u7EBF",
484
+ released: "\u5DF2\u5728 v{version} \u53D1\u5E03 \u2014 \u8BF7\u66F4\u65B0",
485
+ verified: "\u5DF2\u786E\u8BA4\u4FEE\u590D",
486
+ reopened: "\u5DF2\u91CD\u65B0\u6253\u5F00"
487
+ },
488
+ categories: {
489
+ bug: "\u9519\u8BEF",
490
+ slow: "\u6162",
491
+ visual: "\u663E\u793A\u95EE\u9898",
492
+ confusing: "\u96BE\u4EE5\u7406\u89E3",
493
+ idea: "\u60F3\u6CD5"
494
+ },
495
+ ui: {
496
+ yes: "\u662F",
497
+ notYet: "\u8FD8\u6CA1\u6709",
498
+ empty: "\u8FD8\u6CA1\u6709\u53CD\u9988\u3002\u63D0\u4EA4\u540E\u4F1A\u5728\u8FD9\u91CC\u663E\u793A\u8FDB\u5C55\u3002",
499
+ loadError: "\u65E0\u6CD5\u52A0\u8F7D\u66F4\u65B0",
500
+ retry: "\u91CD\u8BD5",
501
+ sending: "\u53D1\u9001\u4E2D\u2026",
502
+ sent: "\u5DF2\u53D1\u9001",
503
+ sendFailed: "\u53D1\u9001\u5931\u8D25",
504
+ noReplies: "\u5F00\u53D1\u8005\u8FD8\u6CA1\u6709\u56DE\u590D\u3002",
505
+ you: "\u4F60",
506
+ developer: "\u5F00\u53D1\u8005",
507
+ addWords: "\u8BF7\u518D\u591A\u5199\u51E0\u4E2A\u5B57",
508
+ developerReplied: "\u5F00\u53D1\u8005\u56DE\u590D\uFF1A\u201C{text}\u201D",
509
+ updates: "\u4F60\u7684\u53CD\u9988\u6709 {n} \u6761\u66F4\u65B0",
510
+ receipt: "\u5DF2\u53D1\u9001\u3002\u6709\u8FDB\u5C55\u65F6\u4F1A\u5728\u8FD9\u91CC\u901A\u77E5\u4F60\u3002",
511
+ trackIt: "\u67E5\u770B\u8FDB\u5C55",
512
+ done: "\u5B8C\u6210",
513
+ emailOptIn: "\u901A\u8FC7\u90AE\u4EF6\u63A5\u6536\u66F4\u65B0",
514
+ emailPlaceholder: "you@example.com",
515
+ emailSubmit: "\u7528\u90AE\u4EF6\u63A5\u6536",
516
+ emailCheckInbox: "\u8BF7\u70B9\u51FB\u6536\u4EF6\u7BB1\u4E2D\u90AE\u4EF6\u91CC\u7684\u94FE\u63A5\u5B8C\u6210\u786E\u8BA4\u3002",
517
+ emailInvalid: "\u90AE\u7BB1\u5730\u5740\u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
518
+ emailFailed: "\u65E0\u6CD5\u8BBE\u7F6E\u90AE\u4EF6\u901A\u77E5\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
519
+ toastReplied: "\u5F00\u53D1\u8005\u56DE\u590D\u4E86\u4F60\u7684\u53CD\u9988",
520
+ toastFixed: "\u4F60\u53CD\u9988\u7684\u95EE\u9898\u5DF2\u4FEE\u590D",
521
+ view: "\u67E5\u770B"
522
+ }
523
+ };
524
+ var COPY = { en: EN, ja: JA, es: ES, th: TH, zh: ZH };
434
525
  function resolveReporterLocale(tag) {
435
526
  const base = (tag ?? "").toLowerCase().split(/[-_]/)[0];
436
527
  return REPORTER_LOCALES.includes(base) ? base : "en";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reporter-ui.ts"],"names":[],"mappings":";AA0BO,IAAM,2BAAA,GAA8B;AAAA,EACzC,KAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF;AAIO,IAAM,gBAAA,GAAmB,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI;AAIhD,IAAM,sBAAsB,CAAC,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,aAAa,MAAM;AAIzE,IAAM,uBAAA,GAA0B;AAAA,EACrC,WAAA;AAAA,EACA,kBAAA;AAAA,EACA,UAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF;AA0BA,IAAM,UAAA,GAAiE;AAAA,EACrE,GAAA,EAAK,UAAA;AAAA,EACL,OAAA,EAAS,UAAA;AAAA,EACT,SAAA,EAAW,UAAA;AAAA,EACX,MAAA,EAAQ,UAAA;AAAA,EACR,UAAA,EAAY,WAAA;AAAA,EACZ,OAAA,EAAS,WAAA;AAAA,EACT,OAAA,EAAS,WAAA;AAAA,EACT,QAAA,EAAU,iBAAA;AAAA,EACV,MAAA,EAAQ,QAAA;AAAA,EACR,WAAA,EAAa,QAAA;AAAA,EACb,QAAA,EAAU,UAAA;AAAA,EACV,KAAA,EAAO,YAAA;AAAA,EACP,QAAA,EAAU,OAAA;AAAA,EACV,SAAA,EAAW;AACb,CAAA;AAEA,IAAM,IAAA,GAAgD;AAAA,EACpD,QAAA,EAAU,SAAA;AAAA,EACV,SAAA,EAAW,MAAA;AAAA,EACX,eAAA,EAAiB,MAAA;AAAA,EACjB,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA,EAAU,eAAA;AAAA,EACV,UAAA,EAAY,SAAA;AAAA,EACZ,aAAA,EAAe,SAAA;AAAA,EACf,KAAA,EAAO,SAAA;AAAA,EACP,MAAA,EAAQ,OAAA;AAAA,EACR,OAAA,EAAS;AACX,CAAA;AAGO,IAAM,uBAAA,GAA0B;AAAA,EACrC,UAAA;AAAA,EACA,WAAA;AAAA,EACA,kBAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF;AA4CA,IAAM,EAAA,GAAmB;AAAA,EACvB,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,UAAA;AAAA,IACV,SAAA,EAAW,iBAAA;AAAA,IACX,eAAA,EAAiB,uBAAA;AAAA,IACjB,MAAA,EAAQ,iBAAA;AAAA,IACR,QAAA,EAAU,iBAAA;AAAA,IACV,UAAA,EAAY,wCAAA;AAAA,IACZ,aAAA,EAAe,qBAAA;AAAA,IACf,KAAA,EAAO,OAAA;AAAA,IACP,MAAA,EAAQ,QAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,SAAA,EAAW,qCAAA;AAAA,IACX,eAAA,EAAiB,+BAAA;AAAA,IACjB,MAAA,EAAQ,2BAAA;AAAA,IACR,QAAA,EAAU,mCAAA;AAAA,IACV,UAAA,EAAY,+BAAA;AAAA,IACZ,aAAA,EAAe,iDAAA;AAAA,IACf,KAAA,EAAO,2BAAA;AAAA,IACP,MAAA,EAAQ,SAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,qBAAA;AAAA,IACV,SAAA,EAAW,qBAAA;AAAA,IACX,MAAA,EAAQ,aAAA;AAAA,IACR,OAAA,EAAS,SAAA;AAAA,IACT,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,SAAA,EAAW,0DAAA;AAAA,IACX,gBAAA,EAAkB,sDAAA;AAAA,IAClB,QAAA,EAAU,gCAAA;AAAA,IACV,mBAAA,EAAqB,6BAAA;AAAA,IACrB,IAAA,EAAM;AAAA,GACR;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,oCAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,mBAAA;AAAA,IACV,SAAA,EAAW,kCAAA;AAAA,IACX,gBAAA,EAAkB,0DAAA;AAAA,IAClB,cAAA,EAAgB,6BAAA;AAAA,IAChB,gBAAA,EAAkB,KAAA;AAAA,IAClB,WAAA,EAAa,sBAAA;AAAA,IACb,KAAA,EAAO,wCAAA;AAAA,IACP,QAAA,EAAU,+CAAA;AAAA,IACV,QAAA,EAAU,0BAAA;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,UAAA,EAAY;AAAA,IACV,GAAA,EAAK,KAAA;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,MAAA,EAAQ,aAAA;AAAA,IACR,SAAA,EAAW,WAAA;AAAA,IACX,IAAA,EAAM;AAAA,GACR;AAAA,EACA,EAAA,EAAI;AAAA,IACF,GAAA,EAAK,KAAA;AAAA,IACL,MAAA,EAAQ,SAAA;AAAA,IACR,KAAA,EAAO,iEAAA;AAAA,IACP,SAAA,EAAW,uBAAA;AAAA,IACX,KAAA,EAAO,OAAA;AAAA,IACP,OAAA,EAAS,eAAA;AAAA,IACT,IAAA,EAAM,MAAA;AAAA,IACN,UAAA,EAAY,QAAA;AAAA,IACZ,SAAA,EAAW,2BAAA;AAAA,IACX,GAAA,EAAK,KAAA;AAAA,IACL,SAAA,EAAW,WAAA;AAAA,IACX,QAAA,EAAU,iBAAA;AAAA,IACV,gBAAA,EAAkB,uCAAA;AAAA,IAClB,OAAA,EAAS,6BAAA;AAAA,IACT,OAAA,EAAS,kDAAA;AAAA,IACT,OAAA,EAAS,UAAA;AAAA,IACT,IAAA,EAAM,MAAA;AAAA,IACN,UAAA,EAAY,sBAAA;AAAA,IACZ,gBAAA,EAAkB,iBAAA;AAAA,IAClB,WAAA,EAAa,kBAAA;AAAA,IACb,eAAA,EAAiB,+CAAA;AAAA,IACjB,YAAA,EAAc,2CAAA;AAAA,IACd,WAAA,EAAa,iDAAA;AAAA,IACb,YAAA,EAAc,sCAAA;AAAA,IACd,UAAA,EAAY,mBAAA;AAAA,IACZ,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,IAAM,EAAA,GAAmB;AAAA,EACvB,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,4CAAA;AAAA,IACV,SAAA,EAAW,oBAAA;AAAA,IACX,eAAA,EAAiB,0BAAA;AAAA,IACjB,MAAA,EAAQ,oBAAA;AAAA,IACR,QAAA,EAAU,4CAAA;AAAA,IACV,UAAA,EAAY,oGAAA;AAAA,IACZ,aAAA,EAAe,2CAAA;AAAA,IACf,KAAA,EAAO,0BAAA;AAAA,IACP,MAAA,EAAQ,cAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,oEAAA;AAAA,IACV,SAAA,EAAW,kGAAA;AAAA,IACX,eAAA,EAAiB,gIAAA;AAAA,IACjB,MAAA,EAAQ,0EAAA;AAAA,IACR,QAAA,EAAU,0HAAA;AAAA,IACV,UAAA,EAAY,sFAAA;AAAA,IACZ,aAAA,EAAe,iJAAA;AAAA,IACf,KAAA,EAAO,kDAAA;AAAA,IACP,MAAA,EAAQ,4CAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,4FAAA;AAAA,IACV,SAAA,EAAW,oBAAA;AAAA,IACX,MAAA,EAAQ,oBAAA;AAAA,IACR,OAAA,EAAS,sCAAA;AAAA,IACT,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,SAAA,EAAW,0KAAA;AAAA,IACX,gBAAA,EAAkB,wJAAA;AAAA,IAClB,QAAA,EAAU,wGAAA;AAAA,IACV,mBAAA,EAAqB,sFAAA;AAAA,IACrB,IAAA,EAAM;AAAA,GACR;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,kGAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,8DAAA;AAAA,IACV,SAAA,EAAW,oEAAA;AAAA,IACX,gBAAA,EAAkB,oKAAA;AAAA,IAClB,cAAA,EAAgB,0DAAA;AAAA,IAChB,gBAAA,EAAkB,oBAAA;AAAA,IAClB,WAAA,EAAa,gCAAA;AAAA,IACb,KAAA,EAAO,oGAAA;AAAA,IACP,QAAA,EAAU,2HAAA;AAAA,IACV,QAAA,EAAU,wDAAA;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,UAAA,EAAY;AAAA,IACV,GAAA,EAAK,cAAA;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,MAAA,EAAQ,4CAAA;AAAA,IACR,SAAA,EAAW,sCAAA;AAAA,IACX,IAAA,EAAM;AAAA,GACR;AAAA,EACA,EAAA,EAAI;AAAA,IACF,GAAA,EAAK,cAAA;AAAA,IACL,MAAA,EAAQ,cAAA;AAAA,IACR,KAAA,EAAO,8JAAA;AAAA,IACP,SAAA,EAAW,gFAAA;AAAA,IACX,KAAA,EAAO,oBAAA;AAAA,IACP,OAAA,EAAS,0BAAA;AAAA,IACT,IAAA,EAAM,sCAAA;AAAA,IACN,UAAA,EAAY,0BAAA;AAAA,IACZ,SAAA,EAAW,wGAAA;AAAA,IACX,GAAA,EAAK,oBAAA;AAAA,IACL,SAAA,EAAW,oBAAA;AAAA,IACX,QAAA,EAAU,sFAAA;AAAA,IACV,gBAAA,EAAkB,qEAAA;AAAA,IAClB,OAAA,EAAS,uGAAA;AAAA,IACT,OAAA,EAAS,kJAAA;AAAA,IACT,OAAA,EAAS,gCAAA;AAAA,IACT,IAAA,EAAM,oBAAA;AAAA,IACN,UAAA,EAAY,oEAAA;AAAA,IACZ,gBAAA,EAAkB,iBAAA;AAAA,IAClB,WAAA,EAAa,kDAAA;AAAA,IACb,eAAA,EAAiB,0KAAA;AAAA,IACjB,YAAA,EAAc,0HAAA;AAAA,IACd,WAAA,EAAa,4LAAA;AAAA,IACb,YAAA,EAAc,wGAAA;AAAA,IACd,UAAA,EAAY,oHAAA;AAAA,IACZ,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,IAAM,EAAA,GAAmB;AAAA,EACvB,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,UAAA;AAAA,IACV,SAAA,EAAW,sBAAA;AAAA,IACX,eAAA,EAAiB,uBAAA;AAAA,IACjB,MAAA,EAAQ,wBAAA;AAAA,IACR,QAAA,EAAU,0BAAA;AAAA,IACV,UAAA,EAAY,0DAAA;AAAA,IACZ,aAAA,EAAe,yBAAA;AAAA,IACf,KAAA,EAAO,WAAA;AAAA,IACP,MAAA,EAAQ,SAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,eAAA;AAAA,IACV,SAAA,EAAW,wCAAA;AAAA,IACX,eAAA,EAAiB,wBAAA;AAAA,IACjB,MAAA,EAAQ,6CAAA;AAAA,IACR,QAAA,EAAU,4CAAA;AAAA,IACV,UAAA,EAAY,oCAAA;AAAA,IACZ,aAAA,EAAe,0CAAA;AAAA,IACf,KAAA,EAAO,kBAAA;AAAA,IACP,MAAA,EAAQ,UAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,qBAAA;AAAA,IACV,SAAA,EAAW,qBAAA;AAAA,IACX,MAAA,EAAQ,eAAA;AAAA,IACR,OAAA,EAAS,WAAA;AAAA,IACT,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,SAAA,EAAW,wDAAA;AAAA,IACX,gBAAA,EAAkB,sDAAA;AAAA,IAClB,QAAA,EAAU,4BAAA;AAAA,IACV,mBAAA,EAAqB,qCAAA;AAAA,IACrB,IAAA,EAAM;AAAA,GACR;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,iDAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,iBAAA;AAAA,IACV,SAAA,EAAW,uCAAA;AAAA,IACX,gBAAA,EAAkB,wDAAA;AAAA,IAClB,cAAA,EAAgB,sCAAA;AAAA,IAChB,gBAAA,EAAkB,OAAA;AAAA,IAClB,WAAA,EAAa,gCAAA;AAAA,IACb,KAAA,EAAO,0DAAA;AAAA,IACP,QAAA,EAAU,iDAAA;AAAA,IACV,QAAA,EAAU,mCAAA;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,UAAA,EAAY;AAAA,IACV,GAAA,EAAK,OAAA;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,MAAA,EAAQ,WAAA;AAAA,IACR,SAAA,EAAW,SAAA;AAAA,IACX,IAAA,EAAM;AAAA,GACR;AAAA,EACA,EAAA,EAAI;AAAA,IACF,GAAA,EAAK,OAAA;AAAA,IACL,MAAA,EAAQ,eAAA;AAAA,IACR,KAAA,EAAO,oEAAA;AAAA,IACP,SAAA,EAAW,qCAAA;AAAA,IACX,KAAA,EAAO,YAAA;AAAA,IACP,OAAA,EAAS,gBAAA;AAAA,IACT,IAAA,EAAM,SAAA;AAAA,IACN,UAAA,EAAY,iBAAA;AAAA,IACZ,SAAA,EAAW,2CAAA;AAAA,IACX,GAAA,EAAK,OAAA;AAAA,IACL,SAAA,EAAW,eAAA;AAAA,IACX,QAAA,EAAU,yBAAA;AAAA,IACV,gBAAA,EAAkB,mDAAA;AAAA,IAClB,OAAA,EAAS,+BAAA;AAAA,IACT,OAAA,EAAS,uDAAA;AAAA,IACT,OAAA,EAAS,UAAA;AAAA,IACT,IAAA,EAAM,OAAA;AAAA,IACN,UAAA,EAAY,8BAAA;AAAA,IACZ,gBAAA,EAAkB,gBAAA;AAAA,IAClB,WAAA,EAAa,oBAAA;AAAA,IACb,eAAA,EAAiB,mDAAA;AAAA,IACjB,YAAA,EAAc,yCAAA;AAAA,IACd,WAAA,EAAa,yEAAA;AAAA,IACb,YAAA,EAAc,4CAAA;AAAA,IACd,UAAA,EAAY,4BAAA;AAAA,IACZ,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,IAAM,EAAA,GAAmB;AAAA,EACvB,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,8DAAA;AAAA,IACV,SAAA,EAAW,0EAAA;AAAA,IACX,eAAA,EAAiB,0HAAA;AAAA,IACjB,MAAA,EAAQ,8DAAA;AAAA,IACR,QAAA,EAAU,kGAAA;AAAA,IACV,UAAA,EAAY,0JAAA;AAAA,IACZ,aAAA,EAAe,mEAAA;AAAA,IACf,KAAA,EAAO,4CAAA;AAAA,IACP,MAAA,EAAQ,4CAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,oHAAA;AAAA,IACV,SAAA,EAAW,8JAAA;AAAA,IACX,eAAA,EAAiB,sNAAA;AAAA,IACjB,MAAA,EAAQ,oHAAA;AAAA,IACR,QAAA,EAAU,sKAAA;AAAA,IACV,UAAA,EAAY,4FAAA;AAAA,IACZ,aAAA,EAAe,6LAAA;AAAA,IACf,KAAA,EAAO,qHAAA;AAAA,IACP,MAAA,EAAQ,4CAAA;AAAA,IACR,OAAA,EAAS;AAAA,GACX;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,QAAA,EAAU,8GAAA;AAAA,IACV,SAAA,EAAW,0EAAA;AAAA,IACX,MAAA,EAAQ,8DAAA;AAAA,IACR,OAAA,EAAS,oEAAA;AAAA,IACT,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,SAAA,EAAW,sTAAA;AAAA,IACX,gBAAA,EAAkB,+PAAA;AAAA,IAClB,QAAA,EAAU,0KAAA;AAAA,IACV,mBAAA,EAAqB,sLAAA;AAAA,IACrB,IAAA,EAAM;AAAA,GACR;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,GAAA,EAAK,kMAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACR;AAAA,EACA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,8GAAA;AAAA,IACV,SAAA,EAAW,0HAAA;AAAA,IACX,gBAAA,EAAkB,4TAAA;AAAA,IAClB,cAAA,EAAgB,8FAAA;AAAA,IAChB,gBAAA,EAAkB,oBAAA;AAAA,IAClB,WAAA,EAAa,8DAAA;AAAA,IACb,KAAA,EAAO,0JAAA;AAAA,IACP,QAAA,EAAU,+MAAA;AAAA,IACV,QAAA,EAAU,oHAAA;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,UAAA,EAAY;AAAA,IACV,GAAA,EAAK,0BAAA;AAAA,IACL,IAAA,EAAM,oBAAA;AAAA,IACN,MAAA,EAAQ,wDAAA;AAAA,IACR,SAAA,EAAW,gCAAA;AAAA,IACX,IAAA,EAAM;AAAA,GACR;AAAA,EACA,EAAA,EAAI;AAAA,IACF,GAAA,EAAK,oBAAA;AAAA,IACL,MAAA,EAAQ,sCAAA;AAAA,IACR,KAAA,EAAO,0SAAA;AAAA,IACP,SAAA,EAAW,gIAAA;AAAA,IACX,KAAA,EAAO,oEAAA;AAAA,IACP,OAAA,EAAS,wDAAA;AAAA,IACT,IAAA,EAAM,4CAAA;AAAA,IACN,UAAA,EAAY,0EAAA;AAAA,IACZ,SAAA,EAAW,kJAAA;AAAA,IACX,GAAA,EAAK,oBAAA;AAAA,IACL,SAAA,EAAW,kDAAA;AAAA,IACX,QAAA,EAAU,8JAAA;AAAA,IACV,gBAAA,EAAkB,0GAAA;AAAA,IAClB,OAAA,EAAS,6MAAA;AAAA,IACT,OAAA,EAAS,qQAAA;AAAA,IACT,OAAA,EAAS,sCAAA;AAAA,IACT,IAAA,EAAM,gCAAA;AAAA,IACN,UAAA,EAAY,sIAAA;AAAA,IACZ,gBAAA,EAAkB,iBAAA;AAAA,IAClB,WAAA,EAAa,sIAAA;AAAA,IACb,eAAA,EAAiB,wPAAA;AAAA,IACjB,YAAA,EAAc,4LAAA;AAAA,IACd,WAAA,EAAa,iRAAA;AAAA,IACb,YAAA,EAAc,4LAAA;AAAA,IACd,UAAA,EAAY,8MAAA;AAAA,IACZ,IAAA,EAAM;AAAA;AAEV,CAAA;AAEA,IAAM,IAAA,GAA6C,EAAE,EAAA,EAAI,EAAA,EAAI,IAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAG;AAG7E,SAAS,sBAAsB,GAAA,EAAgD;AACpF,EAAA,MAAM,IAAA,GAAA,CAAQ,OAAO,EAAA,EAAI,WAAA,GAAc,KAAA,CAAM,MAAM,EAAE,CAAC,CAAA;AACtD,EAAA,OAAQ,gBAAA,CAAuC,QAAA,CAAS,IAAI,CAAA,GAAK,IAAA,GAA0B,IAAA;AAC7F;AAGO,SAAS,YAAA,CAAa,SAAiB,IAAA,EAAoB;AAChE,EAAA,OAAO,IAAA,CAAK,qBAAA,CAAsB,MAAM,CAAC,CAAA;AAC3C;AAGO,SAAS,oBAAA,CACd,QAAA,EACA,MAAA,GAA6D,EAAC,EACtD;AACR,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,YAAA,EAAc,CAAC,GAAG,GAAA,KAAgB;AACxD,IAAA,MAAM,KAAA,GAAQ,OAAO,GAAG,CAAA;AACxB,IAAA,OAAO,UAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,GAAY,EAAA,GAAK,OAAO,KAAK,CAAA;AAAA,EAClE,CAAC,CAAA;AACH;AA6BA,IAAM,SAAA,uBAAgD,GAAA,CAAI;AAAA,EACxD,UAAA;AAAA,EACA,WAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAED,SAAS,YAAY,MAAA,EAAmD;AACtE,EAAA,OAAQ,2BAAA,CAAkD,SAAS,MAAM,CAAA;AAC3E;AAGO,SAAS,eAAe,KAAA,EAA4D;AACzF,EAAA,MAAM,GAAA,GAAA,CAAO,KAAA,CAAM,aAAA,IAAiB,EAAA,EAAI,IAAA,GAAO,WAAA,EAAY,CAAE,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AAGnF,EAAA,OAAO,GAAA,KAAQ,SAAA,IAAa,GAAA,KAAQ,iBAAA,IAAqB,GAAA,KAAQ,MAAA;AACnE;AAOO,SAAS,cAAA,CAAe,KAAA,EAA4B,MAAA,GAAiB,IAAA,EAA0B;AACpG,EAAA,MAAM,IAAA,GAAO,aAAa,MAAM,CAAA;AAChC,EAAA,MAAM,MAAA,GAAA,CAAU,KAAA,CAAM,MAAA,IAAU,EAAA,EAAI,IAAA,EAAK;AAEzC,EAAA,IAAI,MAAyB,WAAA,CAAY,MAAM,CAAA,GAAI,UAAA,CAAW,MAAM,CAAA,GAAI,UAAA;AAExE,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,gBAAA,EAAkB,IAAA,EAAK,IAAK,IAAA;AAClD,EAAA,IAAA,CAAK,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,UAAA,KAAe,SAAS,GAAA,GAAM,eAAA;AAEpE,EAAA,MAAM,WAAW,OAAA,CAAQ,KAAA,CAAM,iBAAiB,CAAA,IAAK,OAAA,CAAQ,MAAM,oBAAoB,CAAA;AACvF,EAAA,IAAI,QAAA,IAAY,SAAA,CAAU,GAAA,CAAI,GAAG,GAAG,GAAA,GAAM,SAAA;AAE1C,EAAA,MAAM,IAAA,GAAO,eAAe,KAAK,CAAA;AACjC,EAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA;AAC3B,EAAA,IAAI,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA;AAC5B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,IAAI,GAAA,KAAQ,UAAA,EAAY,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,QAAA;AAAA,SAAA,IACjC,QAAQ,WAAA,IAAe,GAAA,KAAQ,iBAAA,EAAmB,KAAA,GAAQ,KAAK,IAAA,CAAK,SAAA;AAAA,SAAA,IACpE,GAAA,KAAQ,QAAA,EAAU,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,MAAA;AAAA,SAAA,IACpC,GAAA,KAAQ,eAAA,EAAiB,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,cAAA;AAAA,SAAA,IAC3C,QAAQ,YAAA,IAAgB,GAAA,KAAQ,OAAA,EAAS,KAAA,GAAQ,KAAK,IAAA,CAAK,OAAA;AAAA,EACtE;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,aAAA,IAAiB,IAAA;AACtC,EAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,IAAA,MAAA,GACE,MAAA,IAAU,MAAA,KAAW,MAAA,IAAU,MAAA,IAAU,IAAA,CAAK,YAAA,GAC1C,IAAA,CAAK,YAAA,CAAa,MAA4C,CAAA,GAC9D,IAAA,CAAK,YAAA,CAAa,IAAA;AAAA,EAC1B;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,IAAgB,MAAA;AACrC,EAAA,MAAM,aACJ,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA,IAAK,QAAQ,SAAA,GAC1B,MAAA,KAAW,KAAA,GACT,IAAA,CAAK,OAAO,GAAA,GACZ,MAAA,KAAW,SACT,IAAA,CAAK,MAAA,CAAO,OACZ,IAAA,GACJ,IAAA;AAEN,EAAA,OAAO;AAAA,IACL,GAAA;AAAA,IACA,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAO,EAAE,SAAS,CAAA;AAAA,IAC9C,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,IACd,MAAA,EAAQ,oBAAA,CAAqB,MAAA,EAAQ,EAAE,SAAS,CAAA;AAAA,IAChD,SAAA,EAAW,GAAA,KAAQ,YAAA,IAAgB,GAAA,KAAQ,mBAAmB,GAAA,KAAQ,OAAA;AAAA,IACtE,UAAA;AAAA,IACA,MAAA,EAAQ,GAAA,KAAQ,QAAA,IAAY,MAAA,KAAW;AAAA,GACzC;AACF;AAeO,SAAS,qBACd,IAAA,EACA,MAAA,GAAiC,EAAC,EAClC,SAAiB,IAAA,EACT;AACR,EAAA,MAAM,IAAA,GAAO,aAAa,MAAM,CAAA;AAChC,EAAA,IAAI,IAAA,KAAS,SAAA,EAAW,OAAO,MAAA,CAAO,IAAA,IAAQ,EAAA;AAC9C,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,MAAM,MAAA,GAAS,OAAO,aAAA,IAAiB,EAAA;AACvC,IAAA,OAAO,MAAA,IAAU,MAAA,KAAW,MAAA,IAAU,MAAA,IAAU,IAAA,CAAK,YAAA,GACjD,IAAA,CAAK,YAAA,CAAa,MAA4C,CAAA,GAC9D,IAAA,CAAK,YAAA,CAAa,IAAA;AAAA,EACxB;AACA,EAAA,IAAI,SAAS,kBAAA,EAAoB,OAAO,MAAA,CAAO,IAAA,IAAQ,KAAK,QAAA,CAAS,gBAAA;AACrE,EAAA,OAAO,oBAAA,CAAqB,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AACjG;AAGO,SAAS,qBAAA,CAAsB,QAAA,EAA4B,MAAA,GAAiB,IAAA,EAAc;AAC/F,EAAA,OAAO,YAAA,CAAa,MAAM,CAAA,CAAE,UAAA,CAAW,QAAQ,CAAA;AACjD;AAOO,IAAM,wBAAA,GAA2B;AAGjC,SAAS,sBAAA,CAAuB,KAAyB,aAAA,EAAgC;AAC9F,EAAA,IAAI,eAAe,OAAO,CAAA;AAC1B,EAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,IAAK,GAAA,IAAO,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,wBAAA;AACzF;AAGO,SAAS,eAAA,CAAgB,IAAA,EAAc,aAAA,EAAwB,GAAA,EAAuB;AAC3F,EAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,aAAA,EAAe,OAAO,KAAA;AACvC,EAAA,OAAO,OAAA,CAAQ,MAAA,IAAU,sBAAA,CAAuB,GAAA,EAAK,aAAa,CAAA;AACpE;AAGO,SAAS,qBACd,IAAA,EACwF;AACxF,EAAA,IAAI,CAAC,IAAA,EAAM,OAAO,EAAE,UAAU,OAAA,EAAQ;AACtC,EAAA,IAAI,SAAS,MAAA,EAAQ,OAAO,EAAE,QAAA,EAAU,OAAA,EAAS,cAAc,SAAA,EAAU;AACzE,EAAA,OAAO,EAAE,UAAU,IAAA,EAAK;AAC1B;AAcO,SAAS,uBAAuB,KAAA,EAAqD;AAC1F,EAAA,OAAO,MAAM,IAAA,KAAS,SAAA,IAAa,MAAM,IAAA,KAAS,kBAAA,IAAsB,MAAM,IAAA,KAAS,gBAAA;AACzF;AAGO,SAAS,yBAAA,CAA0B,KAAA,EAA8B,MAAA,GAAiB,IAAA,EAAc;AACrG,EAAA,IAAI,KAAA,CAAM,UAAU,CAAE,uBAAA,CAA8C,SAAS,KAAA,CAAM,IAAI,CAAA,EAAG,OAAO,KAAA,CAAM,IAAA;AACvG,EAAA,OAAO,oBAAA;AAAA,IACL,KAAA,CAAM,IAAA;AAAA,IACN,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,IAAQ,IAAA,EAAM,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW,IAAA,EAAM,aAAA,EAAe,KAAA,CAAM,aAAA,IAAiB,IAAA,EAAK;AAAA,IACvG;AAAA,GACF;AACF;AAEO,IAAM,0BAAA,GAA6B,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK;AAGlD,SAAS,wBAAwB,KAAA,EAM5B;AACV,EAAA,IAAI,CAAC,MAAM,OAAA,IAAW,KAAA,CAAM,eAAe,CAAA,IAAK,KAAA,CAAM,kBAAkB,OAAO,KAAA;AAC/E,EAAA,OAAO,MAAM,WAAA,KAAgB,IAAA,IAAQ,KAAA,CAAM,GAAA,GAAM,MAAM,WAAA,IAAe,0BAAA;AACxE;AAGO,SAAS,oBAAA,CACd,OAAA,EACA,MAAA,GAAiB,IAAA,EACT;AACR,EAAA,MAAM,EAAA,GAAK,YAAA,CAAa,MAAM,CAAA,CAAE,EAAA;AAChC,EAAA,MAAM,IAAA,GAAO,QAAQ,YAAA,KAAiB,CAAA,GAAI,QAAQ,MAAA,CAAO,CAAC,GAAG,IAAA,GAAO,IAAA;AACpE,EAAA,IAAI,IAAA,KAAS,SAAA,IAAa,IAAA,KAAS,gBAAA,SAAyB,EAAA,CAAG,YAAA;AAC/D,EAAA,IAAI,IAAA,KAAS,OAAA,IAAW,IAAA,KAAS,UAAA,SAAmB,EAAA,CAAG,UAAA;AACvD,EAAA,OAAO,GAAG,OAAA,CAAQ,OAAA,CAAQ,OAAO,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAC,CAAA;AAC/D;AAGO,SAAS,yBAAyB,GAAA,EAAsB;AAC7D,EAAA,OAAO,+BAAA,CAAgC,IAAA,CAAK,GAAA,CAAI,IAAA,EAAM,CAAA;AACxD","file":"reporter-ui.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * FILE: reporter-ui.ts\n * PURPOSE: The one reporter-facing vocabulary shared by every SDK widget\n * (web, React Native, Capacitor): report categories, the end-user\n * status table, the timeline kinds and their copy in en/ja/es/th.\n *\n * OVERVIEW:\n * - `reporterStatus(report, locale)` maps all 14 internal `reports.status`\n * values (plus the `awaiting_reporter` and duplicate-group overlays and the\n * feature-request variants) onto eight end-user states. There is no branch\n * that echoes the raw status: an unknown value reads as \"Received\".\n * - `reporterTimelineText(kind, params, locale)` renders pipeline events from\n * a fixed template per kind. Stored notification payload text is never\n * shown, so internal category / severity can not leak to a reporter.\n *\n * USAGE:\n * - `import { reporterStatus } from '@mushi-mushi/core/reporter-ui'`\n * - Shipped as a subpath so the main `@mushi-mushi/core` entry stays within\n * its size budget; nothing here is re-exported from `index.ts`.\n *\n * Plan 018 (docs/execplans/reporter-loop-v2.md) §2.1 and §2.3.\n */\n\n/** Every value `reports_status_check` allows. Pinned to the server list by a test. */\nexport const REPORTER_CANONICAL_STATUSES = [\n 'new',\n 'pending',\n 'submitted',\n 'queued',\n 'classified',\n 'grouped',\n 'fixing',\n 'fixed',\n 'dismissed',\n 'triaged',\n 'in_progress',\n 'resolved',\n 'verified',\n 'reopened',\n] as const;\n\nexport type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];\n\nexport const REPORTER_LOCALES = ['en', 'ja', 'es', 'th'] as const;\nexport type ReporterLocale = (typeof REPORTER_LOCALES)[number];\n\n/** Optional type chips on the one-screen report form, in display order. */\nexport const REPORTER_CATEGORIES = ['bug', 'slow', 'visual', 'confusing', 'idea'] as const;\nexport type ReporterCategory = (typeof REPORTER_CATEGORIES)[number];\n\n/** `closed_reason` values the `reports_closed_reason_check` constraint allows. */\nexport const REPORTER_CLOSED_REASONS = [\n 'duplicate',\n 'not_reproducible',\n 'wont_fix',\n 'working_as_intended',\n 'spam',\n] as const;\nexport type ReporterClosedReason = (typeof REPORTER_CLOSED_REASONS)[number];\n\nexport type ReporterTone =\n | 'neutral'\n | 'info'\n | 'attention'\n | 'progress'\n | 'success'\n | 'success-muted'\n | 'muted';\n\n/** The end-user state a report is shown in. */\nexport type ReporterStatusKey =\n | 'received'\n | 'reviewing'\n | 'reviewing_again'\n | 'fixing'\n | 'verified'\n | 'fixed_next'\n | 'fixed_version'\n | 'fixed'\n | 'closed'\n | 'waiting';\n\n/** Base state per internal status, before overlays. Exhaustive by type. */\nconst BASE_STATE: Record<ReporterCanonicalStatus, ReporterStatusKey> = {\n new: 'received',\n pending: 'received',\n submitted: 'received',\n queued: 'received',\n classified: 'reviewing',\n triaged: 'reviewing',\n grouped: 'reviewing',\n reopened: 'reviewing_again',\n fixing: 'fixing',\n in_progress: 'fixing',\n verified: 'verified',\n fixed: 'fixed_next',\n resolved: 'fixed',\n dismissed: 'closed',\n};\n\nconst TONE: Record<ReporterStatusKey, ReporterTone> = {\n received: 'neutral',\n reviewing: 'info',\n reviewing_again: 'info',\n fixing: 'progress',\n verified: 'success-muted',\n fixed_next: 'success',\n fixed_version: 'success',\n fixed: 'success',\n closed: 'muted',\n waiting: 'attention',\n};\n\n/** Timeline entry kinds the reporter detail view renders (§2.3). */\nexport const REPORTER_TIMELINE_KINDS = [\n 'received',\n 'reviewing',\n 'duplicate_linked',\n 'info_requested',\n 'comment',\n 'reporter_comment',\n 'fix_started',\n 'fixed',\n 'released',\n 'verified',\n 'reopened',\n 'closed',\n] as const;\nexport type ReporterTimelineKind = (typeof REPORTER_TIMELINE_KINDS)[number];\n\nexport interface ReporterCopy {\n status: Record<ReporterStatusKey, string>;\n detail: Record<ReporterStatusKey, string>;\n idea: { received: string; reviewing: string; fixing: string; shipped: string; shippedVersion: string };\n closedReason: Record<Exclude<ReporterClosedReason, 'spam'>, string> & { none: string };\n others: { few: string; many: string };\n timeline: Record<Exclude<ReporterTimelineKind, 'comment' | 'closed'>, string>;\n categories: Record<ReporterCategory, string>;\n ui: {\n yes: string;\n notYet: string;\n empty: string;\n loadError: string;\n retry: string;\n sending: string;\n sent: string;\n sendFailed: string;\n noReplies: string;\n you: string;\n developer: string;\n addWords: string;\n developerReplied: string;\n updates: string;\n /** Receipt after sending (replaces the form in place). */\n receipt: string;\n trackIt: string;\n done: string;\n /** Email opt-in on the receipt / report detail (never pre-ticked). */\n emailOptIn: string;\n emailPlaceholder: string;\n emailSubmit: string;\n emailCheckInbox: string;\n emailInvalid: string;\n emailFailed: string;\n /** Next-visit toast. */\n toastReplied: string;\n toastFixed: string;\n view: string;\n };\n}\n\nconst EN: ReporterCopy = {\n status: {\n received: 'Received',\n reviewing: 'Looking into it',\n reviewing_again: 'Looking into it again',\n fixing: 'Fix in progress',\n verified: 'Confirmed fixed',\n fixed_next: 'Fixed — coming in the next update',\n fixed_version: 'Fixed in v{version}',\n fixed: 'Fixed',\n closed: 'Closed',\n waiting: 'Waiting on you',\n },\n detail: {\n received: 'We got it.',\n reviewing: 'The developer has it on their list.',\n reviewing_again: 'Thanks — we reopened it.',\n fixing: 'A fix is being worked on.',\n verified: 'You confirmed the fix. Thank you!',\n fixed_next: 'It ships in the next release.',\n fixed_version: 'Update to v{version}. Does it work for you now?',\n fixed: 'Does it work for you now?',\n closed: 'Closed.',\n waiting: 'The developer asked a question — reply below.',\n },\n idea: {\n received: 'Thanks for the idea',\n reviewing: 'Under consideration',\n fixing: 'Being built',\n shipped: 'Shipped',\n shippedVersion: 'Shipped in v{version}',\n },\n closedReason: {\n duplicate: \"Same as an earlier report — we'll update you there.\",\n not_reproducible: \"We couldn't reproduce it. Reply if it happens again.\",\n wont_fix: 'We decided not to change this.',\n working_as_intended: 'This is expected behaviour.',\n none: 'Closed.',\n },\n others: {\n few: 'You and a few others reported this',\n many: 'Many people reported this',\n },\n timeline: {\n received: 'You reported this',\n reviewing: 'The developer is looking into it',\n duplicate_linked: \"Same as an existing report — we'll update you there\",\n info_requested: 'The developer asked: {text}',\n reporter_comment: 'You',\n fix_started: 'A fix is in progress',\n fixed: 'Fixed — coming in the next update',\n released: 'Shipped in v{version} — update to get it',\n verified: \"You confirmed it's fixed\",\n reopened: 'Reopened',\n },\n categories: {\n bug: 'Bug',\n slow: 'Slow',\n visual: 'Looks wrong',\n confusing: 'Confusing',\n idea: 'Idea',\n },\n ui: {\n yes: 'Yes',\n notYet: 'Not yet',\n empty: 'Nothing yet — your reports will show up here with updates.',\n loadError: \"Couldn't load updates\",\n retry: 'Retry',\n sending: 'Sending…',\n sent: 'Sent',\n sendFailed: 'Failed',\n noReplies: 'No developer replies yet.',\n you: 'You',\n developer: 'Developer',\n addWords: 'Add a few words',\n developerReplied: 'Developer replied: “{text}”',\n updates: '{n} updates on your reports',\n receipt: \"Sent. We'll let you know here when there's news.\",\n trackIt: 'Track it',\n done: 'Done',\n emailOptIn: 'Get updates by email',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'Email me updates',\n emailCheckInbox: 'Check your inbox and tap the link to confirm.',\n emailInvalid: 'That does not look like an email address.',\n emailFailed: \"Couldn't set up email updates. Try again later.\",\n toastReplied: 'The developer replied to your report',\n toastFixed: 'Your bug is fixed',\n view: 'View',\n },\n};\n\nconst JA: ReporterCopy = {\n status: {\n received: '受け付けました',\n reviewing: '確認中',\n reviewing_again: '再確認中',\n fixing: '修正中',\n verified: '修正を確認済み',\n fixed_next: '修正済み — 次のアップデートで反映',\n fixed_version: 'v{version} で修正済み',\n fixed: '修正済み',\n closed: '終了',\n waiting: 'あなたの返信待ち',\n },\n detail: {\n received: '報告を受け取りました。',\n reviewing: '開発者が対応リストに入れました。',\n reviewing_again: 'ありがとうございます。再度確認しています。',\n fixing: '修正に取り組んでいます。',\n verified: '修正を確認いただきありがとうございます!',\n fixed_next: '次のリリースで反映されます。',\n fixed_version: 'v{version} にアップデートしてください。解決しましたか?',\n fixed: '解決しましたか?',\n closed: '終了しました。',\n waiting: '開発者から質問があります。下から返信してください。',\n },\n idea: {\n received: 'アイデアをありがとうございます',\n reviewing: '検討中',\n fixing: '開発中',\n shipped: 'リリース済み',\n shippedVersion: 'v{version} でリリース済み',\n },\n closedReason: {\n duplicate: '以前の報告と同じ内容です。そちらで進捗をお知らせします。',\n not_reproducible: '再現できませんでした。再発したら返信してください。',\n wont_fix: 'この点は変更しないことにしました。',\n working_as_intended: 'これは想定どおりの動作です。',\n none: '終了しました。',\n },\n others: {\n few: 'あなたを含め数人が報告しています',\n many: '多くの人が報告しています',\n },\n timeline: {\n received: 'あなたが報告しました',\n reviewing: '開発者が確認しています',\n duplicate_linked: '既存の報告と同じ内容です。そちらで進捗をお知らせします',\n info_requested: '開発者からの質問: {text}',\n reporter_comment: 'あなた',\n fix_started: '修正中です',\n fixed: '修正済み — 次のアップデートで反映',\n released: 'v{version} でリリース — アップデートしてください',\n verified: '修正を確認しました',\n reopened: '再オープンしました',\n },\n categories: {\n bug: 'バグ',\n slow: '遅い',\n visual: '表示がおかしい',\n confusing: 'わかりにくい',\n idea: 'アイデア',\n },\n ui: {\n yes: 'はい',\n notYet: 'まだ',\n empty: 'まだありません。報告するとここに進捗が表示されます。',\n loadError: '更新を読み込めませんでした',\n retry: '再試行',\n sending: '送信中…',\n sent: '送信しました',\n sendFailed: '送信失敗',\n noReplies: '開発者からの返信はまだありません。',\n you: 'あなた',\n developer: '開発者',\n addWords: 'もう少し詳しく書いてください',\n developerReplied: '開発者からの返信:「{text}」',\n updates: 'あなたの報告に {n} 件の更新があります',\n receipt: '送信しました。進展があればここでお知らせします。',\n trackIt: '経過を見る',\n done: '閉じる',\n emailOptIn: 'メールで更新を受け取る',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'メールで受け取る',\n emailCheckInbox: '受信トレイのメールのリンクをタップして確認してください。',\n emailInvalid: 'メールアドレスの形式が正しくありません。',\n emailFailed: 'メール通知を設定できませんでした。後でもう一度お試しください。',\n toastReplied: '開発者があなたの報告に返信しました',\n toastFixed: 'あなたが報告した不具合が修正されました',\n view: '見る',\n },\n};\n\nconst ES: ReporterCopy = {\n status: {\n received: 'Recibido',\n reviewing: 'Lo estamos revisando',\n reviewing_again: 'Lo revisamos de nuevo',\n fixing: 'Corrección en curso',\n verified: 'Corrección confirmada',\n fixed_next: 'Corregido — llega en la próxima actualización',\n fixed_version: 'Corregido en v{version}',\n fixed: 'Corregido',\n closed: 'Cerrado',\n waiting: 'Esperando tu respuesta',\n },\n detail: {\n received: 'Lo recibimos.',\n reviewing: 'El desarrollador lo tiene en su lista.',\n reviewing_again: 'Gracias, lo reabrimos.',\n fixing: 'Se está trabajando en una corrección.',\n verified: 'Confirmaste la corrección. ¡Gracias!',\n fixed_next: 'Llega en la próxima versión.',\n fixed_version: 'Actualiza a v{version}. ¿Ya funciona?',\n fixed: '¿Ya funciona?',\n closed: 'Cerrado.',\n waiting: 'El desarrollador hizo una pregunta: responde abajo.',\n },\n idea: {\n received: 'Gracias por la idea',\n reviewing: 'En consideración',\n fixing: 'En desarrollo',\n shipped: 'Publicado',\n shippedVersion: 'Publicado en v{version}',\n },\n closedReason: {\n duplicate: 'Es igual a un reporte anterior; te avisaremos allí.',\n not_reproducible: 'No pudimos reproducirlo. Responde si vuelve a pasar.',\n wont_fix: 'Decidimos no cambiar esto.',\n working_as_intended: 'Este es el comportamiento esperado.',\n none: 'Cerrado.',\n },\n others: {\n few: 'Tú y algunas personas más reportaron esto',\n many: 'Muchas personas reportaron esto',\n },\n timeline: {\n received: 'Reportaste esto',\n reviewing: 'El desarrollador lo está revisando',\n duplicate_linked: 'Es igual a un reporte existente; te avisaremos allí',\n info_requested: 'El desarrollador preguntó: {text}',\n reporter_comment: 'Tú',\n fix_started: 'Hay una corrección en curso',\n fixed: 'Corregido — llega en la próxima actualización',\n released: 'Publicado en v{version}: actualiza para tenerlo',\n verified: 'Confirmaste que está corregido',\n reopened: 'Reabierto',\n },\n categories: {\n bug: 'Error',\n slow: 'Lento',\n visual: 'Se ve mal',\n confusing: 'Confuso',\n idea: 'Idea',\n },\n ui: {\n yes: 'Sí',\n notYet: 'Todavía no',\n empty: 'Nada todavía: tus reportes aparecerán aquí con novedades.',\n loadError: 'No se pudieron cargar las novedades',\n retry: 'Reintentar',\n sending: 'Enviando…',\n sent: 'Enviado',\n sendFailed: 'Error al enviar',\n noReplies: 'El desarrollador aún no ha respondido.',\n you: 'Tú',\n developer: 'Desarrollador',\n addWords: 'Agrega algunas palabras',\n developerReplied: 'El desarrollador respondió: “{text}”',\n updates: '{n} novedades en tus reportes',\n receipt: 'Enviado. Te avisaremos aquí cuando haya novedades.',\n trackIt: 'Seguirlo',\n done: 'Listo',\n emailOptIn: 'Recibir novedades por correo',\n emailPlaceholder: 'tu@ejemplo.com',\n emailSubmit: 'Enviarme novedades',\n emailCheckInbox: 'Revisa tu correo y toca el enlace para confirmar.',\n emailInvalid: 'Eso no parece un correo electrónico.',\n emailFailed: 'No pudimos activar las novedades por correo. Inténtalo más tarde.',\n toastReplied: 'El desarrollador respondió a tu reporte',\n toastFixed: 'Tu error está corregido',\n view: 'Ver',\n },\n};\n\nconst TH: ReporterCopy = {\n status: {\n received: 'ได้รับแล้ว',\n reviewing: 'กำลังตรวจสอบ',\n reviewing_again: 'กำลังตรวจสอบอีกครั้ง',\n fixing: 'กำลังแก้ไข',\n verified: 'ยืนยันว่าแก้แล้ว',\n fixed_next: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n fixed_version: 'แก้แล้วใน v{version}',\n fixed: 'แก้แล้ว',\n closed: 'ปิดแล้ว',\n waiting: 'รอคำตอบจากคุณ',\n },\n detail: {\n received: 'เราได้รับรายงานแล้ว',\n reviewing: 'นักพัฒนาใส่ไว้ในรายการแล้ว',\n reviewing_again: 'ขอบคุณ — เราเปิดเรื่องนี้อีกครั้งแล้ว',\n fixing: 'กำลังดำเนินการแก้ไข',\n verified: 'คุณยืนยันการแก้ไขแล้ว ขอบคุณ!',\n fixed_next: 'จะมาในรุ่นถัดไป',\n fixed_version: 'อัปเดตเป็น v{version} ตอนนี้ใช้ได้หรือยัง?',\n fixed: 'ตอนนี้ใช้ได้หรือยัง?',\n closed: 'ปิดแล้ว',\n waiting: 'นักพัฒนามีคำถาม — ตอบด้านล่าง',\n },\n idea: {\n received: 'ขอบคุณสำหรับไอเดีย',\n reviewing: 'กำลังพิจารณา',\n fixing: 'กำลังสร้าง',\n shipped: 'เปิดใช้แล้ว',\n shippedVersion: 'เปิดใช้แล้วใน v{version}',\n },\n closedReason: {\n duplicate: 'เหมือนกับรายงานก่อนหน้า — เราจะแจ้งความคืบหน้าที่นั่น',\n not_reproducible: 'เราทำให้เกิดซ้ำไม่ได้ ตอบกลับหากเกิดขึ้นอีก',\n wont_fix: 'เราตัดสินใจไม่เปลี่ยนส่วนนี้',\n working_as_intended: 'นี่เป็นการทำงานตามที่ตั้งใจไว้',\n none: 'ปิดแล้ว',\n },\n others: {\n few: 'คุณและอีกไม่กี่คนรายงานเรื่องนี้',\n many: 'มีหลายคนรายงานเรื่องนี้',\n },\n timeline: {\n received: 'คุณรายงานเรื่องนี้',\n reviewing: 'นักพัฒนากำลังตรวจสอบ',\n duplicate_linked: 'เหมือนกับรายงานที่มีอยู่ — เราจะแจ้งความคืบหน้าที่นั่น',\n info_requested: 'นักพัฒนาถามว่า: {text}',\n reporter_comment: 'คุณ',\n fix_started: 'กำลังแก้ไข',\n fixed: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n released: 'เปิดใช้ใน v{version} — อัปเดตเพื่อรับการแก้ไข',\n verified: 'คุณยืนยันว่าแก้แล้ว',\n reopened: 'เปิดอีกครั้ง',\n },\n categories: {\n bug: 'บั๊ก',\n slow: 'ช้า',\n visual: 'แสดงผลผิด',\n confusing: 'สับสน',\n idea: 'ไอเดีย',\n },\n ui: {\n yes: 'ใช่',\n notYet: 'ยังไม่',\n empty: 'ยังไม่มี — รายงานของคุณจะแสดงที่นี่พร้อมความคืบหน้า',\n loadError: 'โหลดความคืบหน้าไม่ได้',\n retry: 'ลองอีกครั้ง',\n sending: 'กำลังส่ง…',\n sent: 'ส่งแล้ว',\n sendFailed: 'ส่งไม่สำเร็จ',\n noReplies: 'ยังไม่มีคำตอบจากนักพัฒนา',\n you: 'คุณ',\n developer: 'นักพัฒนา',\n addWords: 'เพิ่มรายละเอียดอีกเล็กน้อย',\n developerReplied: 'นักพัฒนาตอบว่า: “{text}”',\n updates: 'มีความคืบหน้า {n} รายการในรายงานของคุณ',\n receipt: 'ส่งแล้ว เราจะแจ้งคุณที่นี่เมื่อมีความคืบหน้า',\n trackIt: 'ติดตาม',\n done: 'เสร็จ',\n emailOptIn: 'รับความคืบหน้าทางอีเมล',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'ส่งความคืบหน้าทางอีเมล',\n emailCheckInbox: 'ตรวจสอบกล่องจดหมายแล้วแตะลิงก์เพื่อยืนยัน',\n emailInvalid: 'ดูเหมือนจะไม่ใช่อีเมลที่ถูกต้อง',\n emailFailed: 'ตั้งค่าการแจ้งทางอีเมลไม่สำเร็จ ลองใหม่ภายหลัง',\n toastReplied: 'นักพัฒนาตอบกลับรายงานของคุณแล้ว',\n toastFixed: 'บั๊กที่คุณรายงานได้รับการแก้ไขแล้ว',\n view: 'ดู',\n },\n};\n\nconst COPY: Record<ReporterLocale, ReporterCopy> = { en: EN, ja: JA, es: ES, th: TH };\n\n/** Resolve a BCP-47 tag (`ja-JP`, `es-419`) to a supported locale; English otherwise. */\nexport function resolveReporterLocale(tag: string | null | undefined): ReporterLocale {\n const base = (tag ?? '').toLowerCase().split(/[-_]/)[0];\n return (REPORTER_LOCALES as readonly string[]).includes(base) ? (base as ReporterLocale) : 'en';\n}\n\n/** The full copy table for a locale (unknown tags fall back to English). */\nexport function reporterCopy(locale: string = 'en'): ReporterCopy {\n return COPY[resolveReporterLocale(locale)];\n}\n\n/** Replace `{name}` placeholders; a missing value leaves an empty string. */\nexport function fillReporterTemplate(\n template: string,\n params: Record<string, string | number | null | undefined> = {},\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === null || value === undefined ? '' : String(value);\n });\n}\n\n/** Fields of a reporter-list row `reporterStatus` reads. All but `status` are optional. */\nexport interface ReporterStatusInput {\n status: string;\n /** True (or a timestamp) while the developer waits for the reporter. */\n awaiting_reporter?: boolean | null;\n awaiting_reporter_at?: string | null;\n fixed_in_version?: string | null;\n closed_reason?: string | null;\n /** Bucketed duplicate count; never a raw number. */\n group_bucket?: 'none' | 'few' | 'many' | null;\n /** `user_category`; \"Feature request\" / \"idea\" selects the idea labels. */\n user_category?: string | null;\n}\n\nexport interface ReporterStatusView {\n key: ReporterStatusKey;\n label: string;\n tone: ReporterTone;\n detail: string;\n /** Show the Yes / Not yet verification buttons. */\n canVerify: boolean;\n /** \"You and a few others reported this\", or null. */\n othersNote: string | null;\n /** `closed_reason = 'spam'`: hide the report from the reporter entirely. */\n hidden: boolean;\n}\n\nconst OPEN_KEYS: ReadonlySet<ReporterStatusKey> = new Set([\n 'received',\n 'reviewing',\n 'reviewing_again',\n 'fixing',\n]);\n\nfunction isCanonical(status: string): status is ReporterCanonicalStatus {\n return (REPORTER_CANONICAL_STATUSES as readonly string[]).includes(status);\n}\n\n/** True when the row is a feature request rather than a bug. */\nexport function isReporterIdea(input: Pick<ReporterStatusInput, 'user_category'>): boolean {\n const cat = (input.user_category ?? '').trim().toLowerCase().replace(/[\\s-]+/g, '_');\n // The web widget and the ingest path send 'feature'; older clients sent\n // 'Feature request'. The server's isFeatureRequest() accepts the same set.\n return cat === 'feature' || cat === 'feature_request' || cat === 'idea';\n}\n\n/**\n * The end-user view of a report. Overlays are checked before the base\n * status: \"Waiting on you\" wins for any open report, and the duplicate-group\n * note is added on top of whatever the base state is.\n */\nexport function reporterStatus(input: ReporterStatusInput, locale: string = 'en'): ReporterStatusView {\n const copy = reporterCopy(locale);\n const status = (input.status ?? '').trim();\n // No raw passthrough: anything the table does not know reads as Received.\n let key: ReporterStatusKey = isCanonical(status) ? BASE_STATE[status] : 'received';\n\n const version = input.fixed_in_version?.trim() || null;\n if ((status === 'fixed' || status === 'resolved') && version) key = 'fixed_version';\n\n const awaiting = Boolean(input.awaiting_reporter) || Boolean(input.awaiting_reporter_at);\n if (awaiting && OPEN_KEYS.has(key)) key = 'waiting';\n\n const idea = isReporterIdea(input);\n let label = copy.status[key];\n let detail = copy.detail[key];\n if (idea) {\n if (key === 'received') label = copy.idea.received;\n else if (key === 'reviewing' || key === 'reviewing_again') label = copy.idea.reviewing;\n else if (key === 'fixing') label = copy.idea.fixing;\n else if (key === 'fixed_version') label = copy.idea.shippedVersion;\n else if (key === 'fixed_next' || key === 'fixed') label = copy.idea.shipped;\n }\n\n const reason = input.closed_reason ?? null;\n if (key === 'closed') {\n detail =\n reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n\n const bucket = input.group_bucket ?? 'none';\n const othersNote =\n OPEN_KEYS.has(key) || key === 'waiting'\n ? bucket === 'few'\n ? copy.others.few\n : bucket === 'many'\n ? copy.others.many\n : null\n : null;\n\n return {\n key,\n label: fillReporterTemplate(label, { version }),\n tone: TONE[key],\n detail: fillReporterTemplate(detail, { version }),\n canVerify: key === 'fixed_next' || key === 'fixed_version' || key === 'fixed',\n othersNote,\n hidden: key === 'closed' && reason === 'spam',\n };\n}\n\n/** Parameters a timeline template may interpolate. */\nexport interface ReporterTimelineParams {\n /** Verbatim developer text for `comment` and `info_requested`; the reporter's own for `reporter_comment`. */\n text?: string | null;\n version?: string | null;\n closed_reason?: string | null;\n}\n\n/**\n * Render one timeline entry. Pipeline kinds always come from the template for\n * their kind; only the developer's own words (`comment`, `info_requested`) are\n * passed through, verbatim.\n */\nexport function reporterTimelineText(\n kind: ReporterTimelineKind,\n params: ReporterTimelineParams = {},\n locale: string = 'en',\n): string {\n const copy = reporterCopy(locale);\n if (kind === 'comment') return params.text ?? '';\n if (kind === 'closed') {\n const reason = params.closed_reason ?? '';\n return reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n if (kind === 'reporter_comment') return params.text ?? copy.timeline.reporter_comment;\n return fillReporterTemplate(copy.timeline[kind], { text: params.text, version: params.version });\n}\n\n/** Reporter-facing label for a category chip. */\nexport function reporterCategoryLabel(category: ReporterCategory, locale: string = 'en'): string {\n return reporterCopy(locale).categories[category];\n}\n\n// ---------------------------------------------------------------------------\n// Widget rules shared by web and React Native (Plan 018 §1.1, §2.3, §4.2)\n// ---------------------------------------------------------------------------\n\n/** A few words are enough; an attachment speaks for itself (§1.1). */\nexport const REPORTER_MIN_DESCRIPTION = 8;\n\n/** Characters Send needs: `min` (default 8), or 0 with a screenshot / element attached. */\nexport function reporterRequiredLength(min: number | undefined, hasAttachment: boolean): number {\n if (hasAttachment) return 0;\n return typeof min === 'number' && Number.isFinite(min) && min >= 0 ? Math.floor(min) : REPORTER_MIN_DESCRIPTION;\n}\n\n/** Whether Send is enabled: some text or an attachment, and enough words. */\nexport function reporterCanSend(text: string, hasAttachment: boolean, min?: number): boolean {\n const trimmed = text.trim();\n if (!trimmed && !hasAttachment) return false;\n return trimmed.length >= reporterRequiredLength(min, hasAttachment);\n}\n\n/** Wire category for an optional chip: none → `other` (triage sets the type); Idea → a feature request. */\nexport function reporterChipToReport(\n chip: ReporterCategory | null,\n): { category: 'bug' | 'slow' | 'visual' | 'confusing' | 'other'; userCategory?: string } {\n if (!chip) return { category: 'other' };\n if (chip === 'idea') return { category: 'other', userCategory: 'feature' };\n return { category: chip };\n}\n\n/** One entry of `GET /v1/reporter/reports/:id` → `timeline` (subset the widgets read). */\nexport interface ReporterTimelineEntry {\n kind: string;\n text: string;\n /** Developer- or project-written text: show `text` as is. */\n custom?: boolean;\n body?: string;\n version?: string;\n closed_reason?: string;\n}\n\n/** True for the people talking (developer / reporter), false for pipeline events. */\nexport function isReporterConversation(entry: Pick<ReporterTimelineEntry, 'kind'>): boolean {\n return entry.kind === 'comment' || entry.kind === 'reporter_comment' || entry.kind === 'info_requested';\n}\n\n/** Text for one timeline entry in the reporter's locale; custom wording is shown as is. */\nexport function reporterTimelineEntryText(entry: ReporterTimelineEntry, locale: string = 'en'): string {\n if (entry.custom || !(REPORTER_TIMELINE_KINDS as readonly string[]).includes(entry.kind)) return entry.text;\n return reporterTimelineText(\n entry.kind as ReporterTimelineKind,\n { text: entry.body ?? null, version: entry.version ?? null, closed_reason: entry.closed_reason ?? null },\n locale,\n );\n}\n\nexport const REPORTER_TOAST_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\n/** The next-visit toast: one per session and at most one a day, only with something unread (§4.2). */\nexport function reporterShouldShowToast(input: {\n enabled: boolean;\n unreadTotal: number;\n shownThisSession: boolean;\n lastShownAt: number | null;\n now: number;\n}): boolean {\n if (!input.enabled || input.unreadTotal <= 0 || input.shownThisSession) return false;\n return input.lastShownAt === null || input.now - input.lastShownAt >= REPORTER_TOAST_INTERVAL_MS;\n}\n\n/** \"The developer replied…\", \"Your bug is fixed\", or \"3 updates on your reports\". */\nexport function reporterToastMessage(\n updates: { unread_total: number; latest: Array<{ kind: string | null }> },\n locale: string = 'en',\n): string {\n const ui = reporterCopy(locale).ui;\n const kind = updates.unread_total === 1 ? updates.latest[0]?.kind : null;\n if (kind === 'comment' || kind === 'info_requested') return ui.toastReplied;\n if (kind === 'fixed' || kind === 'released') return ui.toastFixed;\n return ui.updates.replace('{n}', String(updates.unread_total));\n}\n\n/** A typo screen for the email opt-in box; the server validates the address. */\nexport function isPlausibleReporterEmail(raw: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(raw.trim());\n}\n"]}
1
+ {"version":3,"sources":["../src/reporter-ui.ts"],"names":[],"mappings":"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","file":"reporter-ui.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * FILE: reporter-ui.ts\n * PURPOSE: The one reporter-facing vocabulary shared by every SDK widget\n * (web, React Native, Capacitor): report categories, the end-user\n * status table, the timeline kinds and their copy in en/ja/es/th.\n *\n * OVERVIEW:\n * - `reporterStatus(report, locale)` maps all 14 internal `reports.status`\n * values (plus the `awaiting_reporter` and duplicate-group overlays and the\n * feature-request variants) onto eight end-user states. There is no branch\n * that echoes the raw status: an unknown value reads as \"Received\".\n * - `reporterTimelineText(kind, params, locale)` renders pipeline events from\n * a fixed template per kind. Stored notification payload text is never\n * shown, so internal category / severity can not leak to a reporter.\n *\n * USAGE:\n * - `import { reporterStatus } from '@mushi-mushi/core/reporter-ui'`\n * - Shipped as a subpath so the main `@mushi-mushi/core` entry stays within\n * its size budget; nothing here is re-exported from `index.ts`.\n *\n * Plan 018 (docs/execplans/reporter-loop-v2.md) §2.1 and §2.3.\n */\n\n/** Every value `reports_status_check` allows. Pinned to the server list by a test. */\nexport const REPORTER_CANONICAL_STATUSES = [\n 'new',\n 'pending',\n 'submitted',\n 'queued',\n 'classified',\n 'grouped',\n 'fixing',\n 'fixed',\n 'dismissed',\n 'triaged',\n 'in_progress',\n 'resolved',\n 'verified',\n 'reopened',\n] as const;\n\nexport type ReporterCanonicalStatus = (typeof REPORTER_CANONICAL_STATUSES)[number];\n\nexport const REPORTER_LOCALES = ['en', 'ja', 'es', 'th', 'zh'] as const;\nexport type ReporterLocale = (typeof REPORTER_LOCALES)[number];\n\n/** Optional type chips on the one-screen report form, in display order. */\nexport const REPORTER_CATEGORIES = ['bug', 'slow', 'visual', 'confusing', 'idea'] as const;\nexport type ReporterCategory = (typeof REPORTER_CATEGORIES)[number];\n\n/** `closed_reason` values the `reports_closed_reason_check` constraint allows. */\nexport const REPORTER_CLOSED_REASONS = [\n 'duplicate',\n 'not_reproducible',\n 'wont_fix',\n 'working_as_intended',\n 'spam',\n] as const;\nexport type ReporterClosedReason = (typeof REPORTER_CLOSED_REASONS)[number];\n\nexport type ReporterTone =\n | 'neutral'\n | 'info'\n | 'attention'\n | 'progress'\n | 'success'\n | 'success-muted'\n | 'muted';\n\n/** The end-user state a report is shown in. */\nexport type ReporterStatusKey =\n | 'received'\n | 'reviewing'\n | 'reviewing_again'\n | 'fixing'\n | 'verified'\n | 'fixed_next'\n | 'fixed_version'\n | 'fixed'\n | 'closed'\n | 'waiting';\n\n/** Base state per internal status, before overlays. Exhaustive by type. */\nconst BASE_STATE: Record<ReporterCanonicalStatus, ReporterStatusKey> = {\n new: 'received',\n pending: 'received',\n submitted: 'received',\n queued: 'received',\n classified: 'reviewing',\n triaged: 'reviewing',\n grouped: 'reviewing',\n reopened: 'reviewing_again',\n fixing: 'fixing',\n in_progress: 'fixing',\n verified: 'verified',\n fixed: 'fixed_next',\n resolved: 'fixed',\n dismissed: 'closed',\n};\n\nconst TONE: Record<ReporterStatusKey, ReporterTone> = {\n received: 'neutral',\n reviewing: 'info',\n reviewing_again: 'info',\n fixing: 'progress',\n verified: 'success-muted',\n fixed_next: 'success',\n fixed_version: 'success',\n fixed: 'success',\n closed: 'muted',\n waiting: 'attention',\n};\n\n/** Timeline entry kinds the reporter detail view renders (§2.3). */\nexport const REPORTER_TIMELINE_KINDS = [\n 'received',\n 'reviewing',\n 'duplicate_linked',\n 'info_requested',\n 'comment',\n 'reporter_comment',\n 'fix_started',\n 'fixed',\n 'released',\n 'verified',\n 'reopened',\n 'closed',\n] as const;\nexport type ReporterTimelineKind = (typeof REPORTER_TIMELINE_KINDS)[number];\n\nexport interface ReporterCopy {\n status: Record<ReporterStatusKey, string>;\n detail: Record<ReporterStatusKey, string>;\n idea: { received: string; reviewing: string; fixing: string; shipped: string; shippedVersion: string };\n closedReason: Record<Exclude<ReporterClosedReason, 'spam'>, string> & { none: string };\n others: { few: string; many: string };\n timeline: Record<Exclude<ReporterTimelineKind, 'comment' | 'closed'>, string>;\n categories: Record<ReporterCategory, string>;\n ui: {\n yes: string;\n notYet: string;\n empty: string;\n loadError: string;\n retry: string;\n sending: string;\n sent: string;\n sendFailed: string;\n noReplies: string;\n you: string;\n developer: string;\n addWords: string;\n developerReplied: string;\n updates: string;\n /** Receipt after sending (replaces the form in place). */\n receipt: string;\n trackIt: string;\n done: string;\n /** Email opt-in on the receipt / report detail (never pre-ticked). */\n emailOptIn: string;\n emailPlaceholder: string;\n emailSubmit: string;\n emailCheckInbox: string;\n emailInvalid: string;\n emailFailed: string;\n /** Next-visit toast. */\n toastReplied: string;\n toastFixed: string;\n view: string;\n };\n}\n\nconst EN: ReporterCopy = {\n status: {\n received: 'Received',\n reviewing: 'Looking into it',\n reviewing_again: 'Looking into it again',\n fixing: 'Fix in progress',\n verified: 'Confirmed fixed',\n fixed_next: 'Fixed — coming in the next update',\n fixed_version: 'Fixed in v{version}',\n fixed: 'Fixed',\n closed: 'Closed',\n waiting: 'Waiting on you',\n },\n detail: {\n received: 'We got it.',\n reviewing: 'The developer has it on their list.',\n reviewing_again: 'Thanks — we reopened it.',\n fixing: 'A fix is being worked on.',\n verified: 'You confirmed the fix. Thank you!',\n fixed_next: 'It ships in the next release.',\n fixed_version: 'Update to v{version}. Does it work for you now?',\n fixed: 'Does it work for you now?',\n closed: 'Closed.',\n waiting: 'The developer asked a question — reply below.',\n },\n idea: {\n received: 'Thanks for the idea',\n reviewing: 'Under consideration',\n fixing: 'Being built',\n shipped: 'Shipped',\n shippedVersion: 'Shipped in v{version}',\n },\n closedReason: {\n duplicate: \"Same as an earlier report — we'll update you there.\",\n not_reproducible: \"We couldn't reproduce it. Reply if it happens again.\",\n wont_fix: 'We decided not to change this.',\n working_as_intended: 'This is expected behaviour.',\n none: 'Closed.',\n },\n others: {\n few: 'You and a few others reported this',\n many: 'Many people reported this',\n },\n timeline: {\n received: 'You reported this',\n reviewing: 'The developer is looking into it',\n duplicate_linked: \"Same as an existing report — we'll update you there\",\n info_requested: 'The developer asked: {text}',\n reporter_comment: 'You',\n fix_started: 'A fix is in progress',\n fixed: 'Fixed — coming in the next update',\n released: 'Shipped in v{version} — update to get it',\n verified: \"You confirmed it's fixed\",\n reopened: 'Reopened',\n },\n categories: {\n bug: 'Bug',\n slow: 'Slow',\n visual: 'Looks wrong',\n confusing: 'Confusing',\n idea: 'Idea',\n },\n ui: {\n yes: 'Yes',\n notYet: 'Not yet',\n empty: 'Nothing yet — your reports will show up here with updates.',\n loadError: \"Couldn't load updates\",\n retry: 'Retry',\n sending: 'Sending…',\n sent: 'Sent',\n sendFailed: 'Failed',\n noReplies: 'No developer replies yet.',\n you: 'You',\n developer: 'Developer',\n addWords: 'Add a few words',\n developerReplied: 'Developer replied: “{text}”',\n updates: '{n} updates on your reports',\n receipt: \"Sent. We'll let you know here when there's news.\",\n trackIt: 'Track it',\n done: 'Done',\n emailOptIn: 'Get updates by email',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'Email me updates',\n emailCheckInbox: 'Check your inbox and tap the link to confirm.',\n emailInvalid: 'That does not look like an email address.',\n emailFailed: \"Couldn't set up email updates. Try again later.\",\n toastReplied: 'The developer replied to your report',\n toastFixed: 'Your bug is fixed',\n view: 'View',\n },\n};\n\nconst JA: ReporterCopy = {\n status: {\n received: '受け付けました',\n reviewing: '確認中',\n reviewing_again: '再確認中',\n fixing: '修正中',\n verified: '修正を確認済み',\n fixed_next: '修正済み — 次のアップデートで反映',\n fixed_version: 'v{version} で修正済み',\n fixed: '修正済み',\n closed: '終了',\n waiting: 'あなたの返信待ち',\n },\n detail: {\n received: '報告を受け取りました。',\n reviewing: '開発者が対応リストに入れました。',\n reviewing_again: 'ありがとうございます。再度確認しています。',\n fixing: '修正に取り組んでいます。',\n verified: '修正を確認いただきありがとうございます!',\n fixed_next: '次のリリースで反映されます。',\n fixed_version: 'v{version} にアップデートしてください。解決しましたか?',\n fixed: '解決しましたか?',\n closed: '終了しました。',\n waiting: '開発者から質問があります。下から返信してください。',\n },\n idea: {\n received: 'アイデアをありがとうございます',\n reviewing: '検討中',\n fixing: '開発中',\n shipped: 'リリース済み',\n shippedVersion: 'v{version} でリリース済み',\n },\n closedReason: {\n duplicate: '以前の報告と同じ内容です。そちらで進捗をお知らせします。',\n not_reproducible: '再現できませんでした。再発したら返信してください。',\n wont_fix: 'この点は変更しないことにしました。',\n working_as_intended: 'これは想定どおりの動作です。',\n none: '終了しました。',\n },\n others: {\n few: 'あなたを含め数人が報告しています',\n many: '多くの人が報告しています',\n },\n timeline: {\n received: 'あなたが報告しました',\n reviewing: '開発者が確認しています',\n duplicate_linked: '既存の報告と同じ内容です。そちらで進捗をお知らせします',\n info_requested: '開発者からの質問: {text}',\n reporter_comment: 'あなた',\n fix_started: '修正中です',\n fixed: '修正済み — 次のアップデートで反映',\n released: 'v{version} でリリース — アップデートしてください',\n verified: '修正を確認しました',\n reopened: '再オープンしました',\n },\n categories: {\n bug: 'バグ',\n slow: '遅い',\n visual: '表示がおかしい',\n confusing: 'わかりにくい',\n idea: 'アイデア',\n },\n ui: {\n yes: 'はい',\n notYet: 'まだ',\n empty: 'まだありません。報告するとここに進捗が表示されます。',\n loadError: '更新を読み込めませんでした',\n retry: '再試行',\n sending: '送信中…',\n sent: '送信しました',\n sendFailed: '送信失敗',\n noReplies: '開発者からの返信はまだありません。',\n you: 'あなた',\n developer: '開発者',\n addWords: 'もう少し詳しく書いてください',\n developerReplied: '開発者からの返信:「{text}」',\n updates: 'あなたの報告に {n} 件の更新があります',\n receipt: '送信しました。進展があればここでお知らせします。',\n trackIt: '経過を見る',\n done: '閉じる',\n emailOptIn: 'メールで更新を受け取る',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'メールで受け取る',\n emailCheckInbox: '受信トレイのメールのリンクをタップして確認してください。',\n emailInvalid: 'メールアドレスの形式が正しくありません。',\n emailFailed: 'メール通知を設定できませんでした。後でもう一度お試しください。',\n toastReplied: '開発者があなたの報告に返信しました',\n toastFixed: 'あなたが報告した不具合が修正されました',\n view: '見る',\n },\n};\n\nconst ES: ReporterCopy = {\n status: {\n received: 'Recibido',\n reviewing: 'Lo estamos revisando',\n reviewing_again: 'Lo revisamos de nuevo',\n fixing: 'Corrección en curso',\n verified: 'Corrección confirmada',\n fixed_next: 'Corregido — llega en la próxima actualización',\n fixed_version: 'Corregido en v{version}',\n fixed: 'Corregido',\n closed: 'Cerrado',\n waiting: 'Esperando tu respuesta',\n },\n detail: {\n received: 'Lo recibimos.',\n reviewing: 'El desarrollador lo tiene en su lista.',\n reviewing_again: 'Gracias, lo reabrimos.',\n fixing: 'Se está trabajando en una corrección.',\n verified: 'Confirmaste la corrección. ¡Gracias!',\n fixed_next: 'Llega en la próxima versión.',\n fixed_version: 'Actualiza a v{version}. ¿Ya funciona?',\n fixed: '¿Ya funciona?',\n closed: 'Cerrado.',\n waiting: 'El desarrollador hizo una pregunta: responde abajo.',\n },\n idea: {\n received: 'Gracias por la idea',\n reviewing: 'En consideración',\n fixing: 'En desarrollo',\n shipped: 'Publicado',\n shippedVersion: 'Publicado en v{version}',\n },\n closedReason: {\n duplicate: 'Es igual a un reporte anterior; te avisaremos allí.',\n not_reproducible: 'No pudimos reproducirlo. Responde si vuelve a pasar.',\n wont_fix: 'Decidimos no cambiar esto.',\n working_as_intended: 'Este es el comportamiento esperado.',\n none: 'Cerrado.',\n },\n others: {\n few: 'Tú y algunas personas más reportaron esto',\n many: 'Muchas personas reportaron esto',\n },\n timeline: {\n received: 'Reportaste esto',\n reviewing: 'El desarrollador lo está revisando',\n duplicate_linked: 'Es igual a un reporte existente; te avisaremos allí',\n info_requested: 'El desarrollador preguntó: {text}',\n reporter_comment: 'Tú',\n fix_started: 'Hay una corrección en curso',\n fixed: 'Corregido — llega en la próxima actualización',\n released: 'Publicado en v{version}: actualiza para tenerlo',\n verified: 'Confirmaste que está corregido',\n reopened: 'Reabierto',\n },\n categories: {\n bug: 'Error',\n slow: 'Lento',\n visual: 'Se ve mal',\n confusing: 'Confuso',\n idea: 'Idea',\n },\n ui: {\n yes: 'Sí',\n notYet: 'Todavía no',\n empty: 'Nada todavía: tus reportes aparecerán aquí con novedades.',\n loadError: 'No se pudieron cargar las novedades',\n retry: 'Reintentar',\n sending: 'Enviando…',\n sent: 'Enviado',\n sendFailed: 'Error al enviar',\n noReplies: 'El desarrollador aún no ha respondido.',\n you: 'Tú',\n developer: 'Desarrollador',\n addWords: 'Agrega algunas palabras',\n developerReplied: 'El desarrollador respondió: “{text}”',\n updates: '{n} novedades en tus reportes',\n receipt: 'Enviado. Te avisaremos aquí cuando haya novedades.',\n trackIt: 'Seguirlo',\n done: 'Listo',\n emailOptIn: 'Recibir novedades por correo',\n emailPlaceholder: 'tu@ejemplo.com',\n emailSubmit: 'Enviarme novedades',\n emailCheckInbox: 'Revisa tu correo y toca el enlace para confirmar.',\n emailInvalid: 'Eso no parece un correo electrónico.',\n emailFailed: 'No pudimos activar las novedades por correo. Inténtalo más tarde.',\n toastReplied: 'El desarrollador respondió a tu reporte',\n toastFixed: 'Tu error está corregido',\n view: 'Ver',\n },\n};\n\nconst TH: ReporterCopy = {\n status: {\n received: 'ได้รับแล้ว',\n reviewing: 'กำลังตรวจสอบ',\n reviewing_again: 'กำลังตรวจสอบอีกครั้ง',\n fixing: 'กำลังแก้ไข',\n verified: 'ยืนยันว่าแก้แล้ว',\n fixed_next: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n fixed_version: 'แก้แล้วใน v{version}',\n fixed: 'แก้แล้ว',\n closed: 'ปิดแล้ว',\n waiting: 'รอคำตอบจากคุณ',\n },\n detail: {\n received: 'เราได้รับรายงานแล้ว',\n reviewing: 'นักพัฒนาใส่ไว้ในรายการแล้ว',\n reviewing_again: 'ขอบคุณ — เราเปิดเรื่องนี้อีกครั้งแล้ว',\n fixing: 'กำลังดำเนินการแก้ไข',\n verified: 'คุณยืนยันการแก้ไขแล้ว ขอบคุณ!',\n fixed_next: 'จะมาในรุ่นถัดไป',\n fixed_version: 'อัปเดตเป็น v{version} ตอนนี้ใช้ได้หรือยัง?',\n fixed: 'ตอนนี้ใช้ได้หรือยัง?',\n closed: 'ปิดแล้ว',\n waiting: 'นักพัฒนามีคำถาม — ตอบด้านล่าง',\n },\n idea: {\n received: 'ขอบคุณสำหรับไอเดีย',\n reviewing: 'กำลังพิจารณา',\n fixing: 'กำลังสร้าง',\n shipped: 'เปิดใช้แล้ว',\n shippedVersion: 'เปิดใช้แล้วใน v{version}',\n },\n closedReason: {\n duplicate: 'เหมือนกับรายงานก่อนหน้า — เราจะแจ้งความคืบหน้าที่นั่น',\n not_reproducible: 'เราทำให้เกิดซ้ำไม่ได้ ตอบกลับหากเกิดขึ้นอีก',\n wont_fix: 'เราตัดสินใจไม่เปลี่ยนส่วนนี้',\n working_as_intended: 'นี่เป็นการทำงานตามที่ตั้งใจไว้',\n none: 'ปิดแล้ว',\n },\n others: {\n few: 'คุณและอีกไม่กี่คนรายงานเรื่องนี้',\n many: 'มีหลายคนรายงานเรื่องนี้',\n },\n timeline: {\n received: 'คุณรายงานเรื่องนี้',\n reviewing: 'นักพัฒนากำลังตรวจสอบ',\n duplicate_linked: 'เหมือนกับรายงานที่มีอยู่ — เราจะแจ้งความคืบหน้าที่นั่น',\n info_requested: 'นักพัฒนาถามว่า: {text}',\n reporter_comment: 'คุณ',\n fix_started: 'กำลังแก้ไข',\n fixed: 'แก้แล้ว — จะมาในอัปเดตถัดไป',\n released: 'เปิดใช้ใน v{version} — อัปเดตเพื่อรับการแก้ไข',\n verified: 'คุณยืนยันว่าแก้แล้ว',\n reopened: 'เปิดอีกครั้ง',\n },\n categories: {\n bug: 'บั๊ก',\n slow: 'ช้า',\n visual: 'แสดงผลผิด',\n confusing: 'สับสน',\n idea: 'ไอเดีย',\n },\n ui: {\n yes: 'ใช่',\n notYet: 'ยังไม่',\n empty: 'ยังไม่มี — รายงานของคุณจะแสดงที่นี่พร้อมความคืบหน้า',\n loadError: 'โหลดความคืบหน้าไม่ได้',\n retry: 'ลองอีกครั้ง',\n sending: 'กำลังส่ง…',\n sent: 'ส่งแล้ว',\n sendFailed: 'ส่งไม่สำเร็จ',\n noReplies: 'ยังไม่มีคำตอบจากนักพัฒนา',\n you: 'คุณ',\n developer: 'นักพัฒนา',\n addWords: 'เพิ่มรายละเอียดอีกเล็กน้อย',\n developerReplied: 'นักพัฒนาตอบว่า: “{text}”',\n updates: 'มีความคืบหน้า {n} รายการในรายงานของคุณ',\n receipt: 'ส่งแล้ว เราจะแจ้งคุณที่นี่เมื่อมีความคืบหน้า',\n trackIt: 'ติดตาม',\n done: 'เสร็จ',\n emailOptIn: 'รับความคืบหน้าทางอีเมล',\n emailPlaceholder: 'you@example.com',\n emailSubmit: 'ส่งความคืบหน้าทางอีเมล',\n emailCheckInbox: 'ตรวจสอบกล่องจดหมายแล้วแตะลิงก์เพื่อยืนยัน',\n emailInvalid: 'ดูเหมือนจะไม่ใช่อีเมลที่ถูกต้อง',\n emailFailed: 'ตั้งค่าการแจ้งทางอีเมลไม่สำเร็จ ลองใหม่ภายหลัง',\n toastReplied: 'นักพัฒนาตอบกลับรายงานของคุณแล้ว',\n toastFixed: 'บั๊กที่คุณรายงานได้รับการแก้ไขแล้ว',\n view: 'ดู',\n },\n};\n\nconst ZH: ReporterCopy = {\n status: {\n received: '已收到',\n reviewing: '处理中',\n reviewing_again: '重新查看中',\n fixing: '修复中',\n verified: '已确认修复',\n fixed_next: '已修复 — 下次更新上线',\n fixed_version: '已在 v{version} 修复',\n fixed: '已修复',\n closed: '已关闭',\n waiting: '等待你的回复',\n },\n detail: {\n received: '我们已收到你的反馈。',\n reviewing: '开发者已把它加入待处理列表。',\n reviewing_again: '谢谢,我们正在重新查看。',\n fixing: '正在修复。',\n verified: '感谢你确认修复!',\n fixed_next: '会在下一个版本中上线。',\n fixed_version: '请更新到 v{version}。问题解决了吗?',\n fixed: '问题解决了吗?',\n closed: '已关闭。',\n waiting: '开发者有问题想问你,请在下方回复。',\n },\n idea: {\n received: '谢谢你的想法',\n reviewing: '考虑中',\n fixing: '开发中',\n shipped: '已上线',\n shippedVersion: '已在 v{version} 上线',\n },\n closedReason: {\n duplicate: '和之前的一条反馈相同,我们会在那条里通知进展。',\n not_reproducible: '我们没能复现。如果再次发生,请回复。',\n wont_fix: '我们决定不修改这一点。',\n working_as_intended: '这是预期的行为。',\n none: '已关闭。',\n },\n others: {\n few: '包括你在内,有几个人反馈了这个问题',\n many: '很多人反馈了这个问题',\n },\n timeline: {\n received: '你提交了反馈',\n reviewing: '开发者正在查看',\n duplicate_linked: '和已有反馈相同,我们会在那条里通知进展',\n info_requested: '开发者的问题:{text}',\n reporter_comment: '你',\n fix_started: '正在修复',\n fixed: '已修复 — 下次更新上线',\n released: '已在 v{version} 发布 — 请更新',\n verified: '已确认修复',\n reopened: '已重新打开',\n },\n categories: {\n bug: '错误',\n slow: '慢',\n visual: '显示问题',\n confusing: '难以理解',\n idea: '想法',\n },\n ui: {\n yes: '是',\n notYet: '还没有',\n empty: '还没有反馈。提交后会在这里显示进展。',\n loadError: '无法加载更新',\n retry: '重试',\n sending: '发送中…',\n sent: '已发送',\n sendFailed: '发送失败',\n noReplies: '开发者还没有回复。',\n you: '你',\n developer: '开发者',\n addWords: '请再多写几个字',\n developerReplied: '开发者回复:“{text}”',\n updates: '你的反馈有 {n} 条更新',\n receipt: '已发送。有进展时会在这里通知你。',\n trackIt: '查看进展',\n done: '完成',\n emailOptIn: '通过邮件接收更新',\n emailPlaceholder: 'you@example.com',\n emailSubmit: '用邮件接收',\n emailCheckInbox: '请点击收件箱中邮件里的链接完成确认。',\n emailInvalid: '邮箱地址格式不正确。',\n emailFailed: '无法设置邮件通知,请稍后再试。',\n toastReplied: '开发者回复了你的反馈',\n toastFixed: '你反馈的问题已修复',\n view: '查看',\n },\n};\n\nconst COPY: Record<ReporterLocale, ReporterCopy> = { en: EN, ja: JA, es: ES, th: TH, zh: ZH };\n\n/** Resolve a BCP-47 tag (`ja-JP`, `es-419`) to a supported locale; English otherwise. */\nexport function resolveReporterLocale(tag: string | null | undefined): ReporterLocale {\n const base = (tag ?? '').toLowerCase().split(/[-_]/)[0];\n return (REPORTER_LOCALES as readonly string[]).includes(base) ? (base as ReporterLocale) : 'en';\n}\n\n/** The full copy table for a locale (unknown tags fall back to English). */\nexport function reporterCopy(locale: string = 'en'): ReporterCopy {\n return COPY[resolveReporterLocale(locale)];\n}\n\n/** Replace `{name}` placeholders; a missing value leaves an empty string. */\nexport function fillReporterTemplate(\n template: string,\n params: Record<string, string | number | null | undefined> = {},\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === null || value === undefined ? '' : String(value);\n });\n}\n\n/** Fields of a reporter-list row `reporterStatus` reads. All but `status` are optional. */\nexport interface ReporterStatusInput {\n status: string;\n /** True (or a timestamp) while the developer waits for the reporter. */\n awaiting_reporter?: boolean | null;\n awaiting_reporter_at?: string | null;\n fixed_in_version?: string | null;\n closed_reason?: string | null;\n /** Bucketed duplicate count; never a raw number. */\n group_bucket?: 'none' | 'few' | 'many' | null;\n /** `user_category`; \"Feature request\" / \"idea\" selects the idea labels. */\n user_category?: string | null;\n}\n\nexport interface ReporterStatusView {\n key: ReporterStatusKey;\n label: string;\n tone: ReporterTone;\n detail: string;\n /** Show the Yes / Not yet verification buttons. */\n canVerify: boolean;\n /** \"You and a few others reported this\", or null. */\n othersNote: string | null;\n /** `closed_reason = 'spam'`: hide the report from the reporter entirely. */\n hidden: boolean;\n}\n\nconst OPEN_KEYS: ReadonlySet<ReporterStatusKey> = new Set([\n 'received',\n 'reviewing',\n 'reviewing_again',\n 'fixing',\n]);\n\nfunction isCanonical(status: string): status is ReporterCanonicalStatus {\n return (REPORTER_CANONICAL_STATUSES as readonly string[]).includes(status);\n}\n\n/** True when the row is a feature request rather than a bug. */\nexport function isReporterIdea(input: Pick<ReporterStatusInput, 'user_category'>): boolean {\n const cat = (input.user_category ?? '').trim().toLowerCase().replace(/[\\s-]+/g, '_');\n // The web widget and the ingest path send 'feature'; older clients sent\n // 'Feature request'. The server's isFeatureRequest() accepts the same set.\n return cat === 'feature' || cat === 'feature_request' || cat === 'idea';\n}\n\n/**\n * The end-user view of a report. Overlays are checked before the base\n * status: \"Waiting on you\" wins for any open report, and the duplicate-group\n * note is added on top of whatever the base state is.\n */\nexport function reporterStatus(input: ReporterStatusInput, locale: string = 'en'): ReporterStatusView {\n const copy = reporterCopy(locale);\n const status = (input.status ?? '').trim();\n // No raw passthrough: anything the table does not know reads as Received.\n let key: ReporterStatusKey = isCanonical(status) ? BASE_STATE[status] : 'received';\n\n const version = input.fixed_in_version?.trim() || null;\n if ((status === 'fixed' || status === 'resolved') && version) key = 'fixed_version';\n\n const awaiting = Boolean(input.awaiting_reporter) || Boolean(input.awaiting_reporter_at);\n if (awaiting && OPEN_KEYS.has(key)) key = 'waiting';\n\n const idea = isReporterIdea(input);\n let label = copy.status[key];\n let detail = copy.detail[key];\n if (idea) {\n if (key === 'received') label = copy.idea.received;\n else if (key === 'reviewing' || key === 'reviewing_again') label = copy.idea.reviewing;\n else if (key === 'fixing') label = copy.idea.fixing;\n else if (key === 'fixed_version') label = copy.idea.shippedVersion;\n else if (key === 'fixed_next' || key === 'fixed') label = copy.idea.shipped;\n }\n\n const reason = input.closed_reason ?? null;\n if (key === 'closed') {\n detail =\n reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n\n const bucket = input.group_bucket ?? 'none';\n const othersNote =\n OPEN_KEYS.has(key) || key === 'waiting'\n ? bucket === 'few'\n ? copy.others.few\n : bucket === 'many'\n ? copy.others.many\n : null\n : null;\n\n return {\n key,\n label: fillReporterTemplate(label, { version }),\n tone: TONE[key],\n detail: fillReporterTemplate(detail, { version }),\n canVerify: key === 'fixed_next' || key === 'fixed_version' || key === 'fixed',\n othersNote,\n hidden: key === 'closed' && reason === 'spam',\n };\n}\n\n/** Parameters a timeline template may interpolate. */\nexport interface ReporterTimelineParams {\n /** Verbatim developer text for `comment` and `info_requested`; the reporter's own for `reporter_comment`. */\n text?: string | null;\n version?: string | null;\n closed_reason?: string | null;\n}\n\n/**\n * Render one timeline entry. Pipeline kinds always come from the template for\n * their kind; only the developer's own words (`comment`, `info_requested`) are\n * passed through, verbatim.\n */\nexport function reporterTimelineText(\n kind: ReporterTimelineKind,\n params: ReporterTimelineParams = {},\n locale: string = 'en',\n): string {\n const copy = reporterCopy(locale);\n if (kind === 'comment') return params.text ?? '';\n if (kind === 'closed') {\n const reason = params.closed_reason ?? '';\n return reason && reason !== 'spam' && reason in copy.closedReason\n ? copy.closedReason[reason as keyof ReporterCopy['closedReason']]\n : copy.closedReason.none;\n }\n if (kind === 'reporter_comment') return params.text ?? copy.timeline.reporter_comment;\n return fillReporterTemplate(copy.timeline[kind], { text: params.text, version: params.version });\n}\n\n/** Reporter-facing label for a category chip. */\nexport function reporterCategoryLabel(category: ReporterCategory, locale: string = 'en'): string {\n return reporterCopy(locale).categories[category];\n}\n\n// ---------------------------------------------------------------------------\n// Widget rules shared by web and React Native (Plan 018 §1.1, §2.3, §4.2)\n// ---------------------------------------------------------------------------\n\n/** A few words are enough; an attachment speaks for itself (§1.1). */\nexport const REPORTER_MIN_DESCRIPTION = 8;\n\n/** Characters Send needs: `min` (default 8), or 0 with a screenshot / element attached. */\nexport function reporterRequiredLength(min: number | undefined, hasAttachment: boolean): number {\n if (hasAttachment) return 0;\n return typeof min === 'number' && Number.isFinite(min) && min >= 0 ? Math.floor(min) : REPORTER_MIN_DESCRIPTION;\n}\n\n/** Whether Send is enabled: some text or an attachment, and enough words. */\nexport function reporterCanSend(text: string, hasAttachment: boolean, min?: number): boolean {\n const trimmed = text.trim();\n if (!trimmed && !hasAttachment) return false;\n return trimmed.length >= reporterRequiredLength(min, hasAttachment);\n}\n\n/** Wire category for an optional chip: none → `other` (triage sets the type); Idea → a feature request. */\nexport function reporterChipToReport(\n chip: ReporterCategory | null,\n): { category: 'bug' | 'slow' | 'visual' | 'confusing' | 'other'; userCategory?: string } {\n if (!chip) return { category: 'other' };\n if (chip === 'idea') return { category: 'other', userCategory: 'feature' };\n return { category: chip };\n}\n\n/** One entry of `GET /v1/reporter/reports/:id` → `timeline` (subset the widgets read). */\nexport interface ReporterTimelineEntry {\n kind: string;\n text: string;\n /** Developer- or project-written text: show `text` as is. */\n custom?: boolean;\n body?: string;\n version?: string;\n closed_reason?: string;\n}\n\n/** True for the people talking (developer / reporter), false for pipeline events. */\nexport function isReporterConversation(entry: Pick<ReporterTimelineEntry, 'kind'>): boolean {\n return entry.kind === 'comment' || entry.kind === 'reporter_comment' || entry.kind === 'info_requested';\n}\n\n/** Text for one timeline entry in the reporter's locale; custom wording is shown as is. */\nexport function reporterTimelineEntryText(entry: ReporterTimelineEntry, locale: string = 'en'): string {\n if (entry.custom || !(REPORTER_TIMELINE_KINDS as readonly string[]).includes(entry.kind)) return entry.text;\n return reporterTimelineText(\n entry.kind as ReporterTimelineKind,\n { text: entry.body ?? null, version: entry.version ?? null, closed_reason: entry.closed_reason ?? null },\n locale,\n );\n}\n\nexport const REPORTER_TOAST_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\n/** The next-visit toast: one per session and at most one a day, only with something unread (§4.2). */\nexport function reporterShouldShowToast(input: {\n enabled: boolean;\n unreadTotal: number;\n shownThisSession: boolean;\n lastShownAt: number | null;\n now: number;\n}): boolean {\n if (!input.enabled || input.unreadTotal <= 0 || input.shownThisSession) return false;\n return input.lastShownAt === null || input.now - input.lastShownAt >= REPORTER_TOAST_INTERVAL_MS;\n}\n\n/** \"The developer replied…\", \"Your bug is fixed\", or \"3 updates on your reports\". */\nexport function reporterToastMessage(\n updates: { unread_total: number; latest: Array<{ kind: string | null }> },\n locale: string = 'en',\n): string {\n const ui = reporterCopy(locale).ui;\n const kind = updates.unread_total === 1 ? updates.latest[0]?.kind : null;\n if (kind === 'comment' || kind === 'info_requested') return ui.toastReplied;\n if (kind === 'fixed' || kind === 'released') return ui.toastFixed;\n return ui.updates.replace('{n}', String(updates.unread_total));\n}\n\n/** A typo screen for the email opt-in box; the server validates the address. */\nexport function isPlausibleReporterEmail(raw: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(raw.trim());\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/core",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Types, API client and pre-filter for Mushi SDKs. The bug mediator for AI-built apps: plain-English diagnosis + a ready fix, in your editor.",
5
5
  "license": "MIT",
6
6
  "type": "module",