@@ -24,6 +24,11 @@ The secret files must be readable by UID 10001 in the container. Root-owned mode
2424` 0444 ` is suitable because Docker bind-mounts them read-only. The application
2525configuration itself is committed as ` config/sites.toml ` .
2626
27+ The process is configured with explicit command-line arguments rather than
28+ environment variables. ` python -m feedback --help ` lists the config, secret-file,
29+ listener, proxy-trust, concurrency, and keep-alive options. Docker Compose passes
30+ those arguments directly and uses fixed read-only mounts.
31+
2732The ` vps-8def0ca8 ` NixOS configuration imports and enables the feedback service.
2833Rebuilding that host builds the image from ` /srv/feedback ` , starts it with Docker
2934Compose, and provisions the ` feedback-api.cpp.social ` nginx virtual host and ACME
@@ -36,6 +41,77 @@ systemctl status feedback-service
3641curl --fail --silent --show-error https://feedback-api.cpp.social/
3742```
3843
44+ ## Site mappings and metadata
45+
46+ Each site maps a browser resource to one GitHub Discussion. The configured
47+ ` mapping ` selects the value used as the discussion lookup term and title:
48+
49+ - ` key ` : the stable application-provided ` resource.key ` (recommended when URLs or
50+ titles may change).
51+ - ` title ` : the resource title.
52+ - ` url ` : the complete canonical URL, including its origin.
53+ - ` pathname ` : only the canonical URL path, allowing equivalent pages on multiple
54+ origins to share a discussion.
55+ - ` custom ` : an arbitrary consumer-provided string for custom routing schemes.
56+ - ` number ` : the numeric number of an already existing GitHub Discussion. Unlike
57+ ` key ` , it is not a resource identifier and missing discussions are never created.
58+
59+ The browser package exports ` resourceFromDocument() ` . By default it reads the
60+ first non-empty title from ` meta[property="og:title"] ` and then ` <title> ` , reads
61+ ` link[rel="canonical"] ` when present, and otherwise uses the current location.
62+ Consumers can override ` titleSelectors ` and ` canonicalSelector ` , or construct a
63+ ` Resource ` directly and supply ` custom ` , ` pathname ` , or ` number ` . Metadata
64+ selection is intentionally client-side; the service only receives validated
65+ resource values and applies the site mapping.
66+
67+ ``` ts
68+ const resource = resourceFromDocument ({
69+ key: " articles/stable-id" ,
70+ titleSelectors: [' meta[name="feedback-title"]' , ' meta[property="og:title"]' , " title" ],
71+ canonicalSelector: ' link[rel="canonical"]' ,
72+ custom: document .body .dataset .feedbackKey ?? " default-feedback-key" ,
73+ });
74+ ```
75+
76+ ## Counter cache
77+
78+ ` cache_fresh_seconds ` is the age at which a requested tracked counter needs an
79+ authoritative GitHub refresh. The default is five seconds. A batched request
80+ refreshes only stale resources; recently refreshed resources in the same request
81+ are served from SQLite. For example, if A was last refreshed 30 seconds ago and B
82+ three seconds ago, requesting ` [A, B] ` sends only A's discussion ID to GitHub and
83+ returns B from SQLite in the same response.
84+
85+ ` refresh_cooldown_seconds ` is the minimum delay before retrying a refresh attempt
86+ for the same resource. It primarily prevents repeated GitHub calls after a failed
87+ or concurrent attempt. Concurrent requests also join the same in-flight site
88+ batch. The default is five seconds.
89+
90+ ` refresh_sweep_seconds ` controls the low-priority full maintenance cycle. The
91+ default is 86400 seconds (daily). The service walks only discussions whose last
92+ authoritative snapshot is that old, in batches of 50 with pacing between full
93+ batches. Targeted requests and successful votes continue independently.
94+
95+ Votes made through the runtime update SQLite immediately using an atomic,
96+ confirmed delta. These local values are tentative: they do not change the last
97+ GitHub-refresh timestamp, and the next successful targeted or maintenance refresh
98+ replaces them with GitHub's absolute counts. Counter responses use ` no-cache ` , so
99+ browsers revalidate with this service; that does not imply a GitHub request while
100+ the relevant snapshot remains fresh.
101+
102+ The browser runtime keeps the last counter snapshot in local storage for up to
103+ seven days. Consumers can render it synchronously while the API request is in
104+ flight, avoiding a flash of zero counters. Snapshots contain only the site/resource
105+ key, discussion node ID, counts, and save time; authentication tokens are not part
106+ of this cache. Storage is optional and failures fall back to the network normally.
107+
108+ Operational logs are emitted at GitHub boundaries rather than for every HTTP
109+ request. Reaction-refresh lines include the site, trigger (` requested ` or
110+ ` sweep ` ), batch size, updated-row count, and duration. Failures include safe
111+ GitHub status/request IDs where available. Discussion discovery/creation, OAuth
112+ failures, vote failures, startup, and sweep summaries are also logged. Client
113+ IPs, origins, resource URLs, authorization codes, and tokens are not logged.
114+
39115## Local checks
40116
41117``` sh
0 commit comments