snagset 0.0.1
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/astro.d.ts +48 -0
- package/dist/astro.js +50 -0
- package/dist/astro.js.map +1 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/next.d.ts +51 -0
- package/dist/next.js +17 -0
- package/dist/next.js.map +1 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +19 -0
- package/dist/react.js.map +1 -0
- package/dist/vendor/types/index.d.ts +1 -0
- package/dist/vendor/types/index.js +4 -0
- package/dist/vendor/types/names.d.ts +207 -0
- package/dist/vendor/types/names.js +254 -0
- package/dist/vite.d.ts +51 -0
- package/dist/vite.js +20 -0
- package/dist/vite.js.map +1 -0
- package/dist/vue.d.ts +65 -0
- package/dist/vue.js +55 -0
- package/dist/vue.js.map +1 -0
- package/package.json +70 -0
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type InitOptions } from "./index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset/react` — D63, and the twenty-line wrapper D37 predicted.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { Snagset } from "snagset/react";
|
|
7
|
+
*
|
|
8
|
+
* <Snagset host="https://review.example.com" site="prj_…" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* It renders NOTHING. There is no element to place, no wrapper div, no portal
|
|
12
|
+
* — the widget owns its own root in the DOM (02 §3's shadow host) and this
|
|
13
|
+
* component's whole job is to run `init()` while it is on screen. Returning
|
|
14
|
+
* `null` is the honest expression of that, and it means the component can sit
|
|
15
|
+
* anywhere in a tree without affecting layout.
|
|
16
|
+
*
|
|
17
|
+
* `react` is an OPTIONAL peer dependency, so installing `snagset` in a Vue or
|
|
18
|
+
* vanilla project pulls in no React and warns about nothing.
|
|
19
|
+
*
|
|
20
|
+
* `.ts`, NOT `.tsx`, because there is no JSX in it — a component that renders
|
|
21
|
+
* nothing has nothing to render. The extension was `.tsx` out of habit, and it
|
|
22
|
+
* cost something real: Node's type stripping handles `.ts` and refuses `.tsx`
|
|
23
|
+
* (JSX needs a transform, not a strip), so the test that imports every subpath
|
|
24
|
+
* to check its default export could not load this one.
|
|
25
|
+
*/
|
|
26
|
+
export type SnagsetProps = InitOptions;
|
|
27
|
+
export declare function Snagset(props: SnagsetProps): null;
|
|
28
|
+
export default Snagset;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { init } from "./index.js";
|
|
3
|
+
export function Snagset(props) {
|
|
4
|
+
// Every value is in the dependency list via JSON, not the object identity:
|
|
5
|
+
// a caller writing `<Snagset host={…} site={…} />` creates a new props object
|
|
6
|
+
// every render, so depending on the object would tear down and re-mount the
|
|
7
|
+
// widget on every parent render. This is the one place in twenty lines where
|
|
8
|
+
// the obvious version is wrong.
|
|
9
|
+
const key = JSON.stringify(props);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// `init` returns its own teardown and is idempotent — which is what makes
|
|
12
|
+
// this safe under React 18 strict mode, where effects run twice in
|
|
13
|
+
// development and a naive implementation boots two widgets.
|
|
14
|
+
return init(JSON.parse(key));
|
|
15
|
+
}, [key]);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export default Snagset;
|
|
19
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,IAAI,EAAoB,MAAM,YAAY,CAAC;AA6BpD,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,gCAAgC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAClC,SAAS,CAAC,GAAG,EAAE;QACb,0EAA0E;QAC1E,mEAAmE;QACnE,4DAA4D;QAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC,CAAC;IAC9C,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACV,OAAO,IAAI,CAAC;AACd,CAAC;AAED,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./names.ts";
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** The one place the product name exists. Everything else derives from this. */
|
|
2
|
+
export declare const BRAND: {
|
|
3
|
+
readonly $comment: "The ONLY place the product name appears. `long` = display/global name. `short` = the terse prefix that goes on attributes, classes, params, storage keys and artefact filenames (a client's DOM should not carry a long word). `pkg` = the npm/lowercase form used for env vars, headers, metrics, DOM events and symbols.";
|
|
4
|
+
readonly long: "Snagset";
|
|
5
|
+
readonly short: "snag";
|
|
6
|
+
readonly pkg: "snagset";
|
|
7
|
+
};
|
|
8
|
+
export declare const PRODUCT: "Snagset";
|
|
9
|
+
export declare const NPM_PACKAGE: "snagset";
|
|
10
|
+
export declare const NPM_SCOPE: "@snagset";
|
|
11
|
+
/** Frozen once v1 stores real data — stamped into exports, issue bodies and the MCP contract, so it must survive a rename (registry.frozen). */
|
|
12
|
+
export declare const SCHEMA_VERSION: "snagset.v1";
|
|
13
|
+
export declare const CSS_VAR_PREFIX: "--snag-";
|
|
14
|
+
export declare const HTTP_HEADER_PREFIX: "x-snagset-";
|
|
15
|
+
export declare const ENV_PREFIX: "SNAGSET_";
|
|
16
|
+
export declare const PG_TYPE_PREFIX: "snag_";
|
|
17
|
+
export declare const METRIC_PREFIX: "snagset_";
|
|
18
|
+
export declare const DOM_EVENT_PREFIX: "snagset:";
|
|
19
|
+
export declare const SYMBOL_PREFIX: "snagset.";
|
|
20
|
+
export declare const MENTION_SCHEME: "snag:user/";
|
|
21
|
+
export declare const SKIP_HEADER: "x-snagset-skip";
|
|
22
|
+
export declare const THREAD_REF_PREFIX: "SN-";
|
|
23
|
+
export declare const TERM_SET: "Snagset";
|
|
24
|
+
export declare const TERM_SET_PLURAL: "Snagsets";
|
|
25
|
+
export declare const TERM_ITEM: "Snag";
|
|
26
|
+
export declare const TERM_ITEM_PLURAL: "Snags";
|
|
27
|
+
export declare const RESERVED_CUSTOM_PREFIX: "cr.";
|
|
28
|
+
/** @returns e.g. `x-snagset-idempotency-key` */
|
|
29
|
+
export declare const header: (name: string) => string;
|
|
30
|
+
/** @returns e.g. `SNAGSET_SESSION_TTL_S` */
|
|
31
|
+
export declare const env: (name: string) => string;
|
|
32
|
+
/** @returns e.g. `snag_attachment_kind` */
|
|
33
|
+
export declare const pgType: (name: string) => string;
|
|
34
|
+
/** @returns e.g. `snagset_http_requests_total` */
|
|
35
|
+
export declare const metric: (name: string) => string;
|
|
36
|
+
/** window.* names (§2.2). Dunders are internal/test affordances, not public API. */
|
|
37
|
+
export declare const GLOBALS: {
|
|
38
|
+
readonly api: "Snagset";
|
|
39
|
+
readonly config: "SnagsetConfig";
|
|
40
|
+
readonly disabled: "__SNAGSET_DISABLED__";
|
|
41
|
+
readonly assetBase: "__SNAGSET_ASSET_BASE__";
|
|
42
|
+
readonly collisionFallback: "__snagset__";
|
|
43
|
+
};
|
|
44
|
+
/** data-*-attributes on the host <script> tag (§2.3). */
|
|
45
|
+
export type ScriptAttr = "site" | "host" | "disabled" | "env" | "mode" | "badge" | "lang" | "theme" | "position" | "offset" | "assetBase" | "branch" | "build" | "storage" | "hotkey" | "allowAutomation" | "screenshots" | "maskSelectors" | "nonce" | "autoMount";
|
|
46
|
+
export declare const scriptAttr: (name: ScriptAttr) => string;
|
|
47
|
+
/** DOM the widget authors on itself (§2.3b). */
|
|
48
|
+
export type WidgetAttr = "root";
|
|
49
|
+
export declare const widgetAttr: (name: WidgetAttr) => string;
|
|
50
|
+
/** Attributes the host application may author (§2.4). */
|
|
51
|
+
export type HostAttr = "anchor" | "ignore" | "mask" | "nomask" | "src" | "component";
|
|
52
|
+
export declare const hostAttr: (name: HostAttr) => string;
|
|
53
|
+
/** Class-form masking hooks (§2.4). */
|
|
54
|
+
export type CssClass = "mask" | "block";
|
|
55
|
+
export declare const cssClass: (name: CssClass) => string;
|
|
56
|
+
/** Custom properties (§2.1's prefix, enumerated). 06 §14 makes every one of these public SemVer surface, so they are registered rather than minted at the point of use. Values are NOT here — the registry governs names, never design. */
|
|
57
|
+
export declare const CSS_VAR: {
|
|
58
|
+
readonly font: "--snag-font";
|
|
59
|
+
readonly accent: "--snag-accent";
|
|
60
|
+
readonly onAccent: "--snag-onAccent";
|
|
61
|
+
readonly bg: "--snag-bg";
|
|
62
|
+
readonly fg: "--snag-fg";
|
|
63
|
+
readonly muted: "--snag-muted";
|
|
64
|
+
readonly border: "--snag-border";
|
|
65
|
+
readonly radius: "--snag-radius";
|
|
66
|
+
readonly shadow: "--snag-shadow";
|
|
67
|
+
readonly z: "--snag-z";
|
|
68
|
+
readonly motion: "--snag-motion";
|
|
69
|
+
readonly surface: "--snag-surface";
|
|
70
|
+
readonly sunken: "--snag-sunken";
|
|
71
|
+
readonly hover: "--snag-hover";
|
|
72
|
+
readonly focus: "--snag-focus";
|
|
73
|
+
readonly disabled: "--snag-disabled";
|
|
74
|
+
readonly statusOpen: "--snag-status-open";
|
|
75
|
+
readonly statusProgress: "--snag-status-progress";
|
|
76
|
+
readonly statusReview: "--snag-status-review";
|
|
77
|
+
readonly statusResolved: "--snag-status-resolved";
|
|
78
|
+
readonly anchorOrphan: "--snag-anchor-orphan";
|
|
79
|
+
readonly tbH: "--snag-tb-h";
|
|
80
|
+
readonly dockW: "--snag-dock-w";
|
|
81
|
+
readonly kb: "--snag-kb";
|
|
82
|
+
readonly thumbX: "--snag-thumb-x";
|
|
83
|
+
readonly thumbW: "--snag-thumb-w";
|
|
84
|
+
};
|
|
85
|
+
/** Which tokens belong to which role — the dark block must override theme members only. */
|
|
86
|
+
export declare const CSS_VAR_GROUPS: {
|
|
87
|
+
readonly theme: readonly ["font", "accent", "onAccent", "bg", "fg", "muted", "border", "radius", "shadow", "z", "motion"];
|
|
88
|
+
readonly derived: readonly ["surface", "sunken", "hover", "focus", "disabled"];
|
|
89
|
+
readonly status: readonly ["statusOpen", "statusProgress", "statusReview", "statusResolved"];
|
|
90
|
+
readonly anchor: readonly ["anchorOrphan"];
|
|
91
|
+
readonly geometry: readonly ["tbH", "dockW", "kb", "thumbX", "thumbW"];
|
|
92
|
+
};
|
|
93
|
+
export type CssVar = keyof typeof CSS_VAR;
|
|
94
|
+
/** @returns e.g. `--snag-bg` */
|
|
95
|
+
export declare const cssVar: (name: CssVar) => string;
|
|
96
|
+
/** @returns e.g. `var(--snag-bg)` — what stylesheets actually interpolate. */
|
|
97
|
+
export declare const cssRef: (name: CssVar) => string;
|
|
98
|
+
export type QueryParam = "t" | "off" | "debug" | "theme";
|
|
99
|
+
export type FragmentParam = "k";
|
|
100
|
+
/** @returns e.g. `snag_t`. The review-link secret is a FRAGMENT param (§2.5). */
|
|
101
|
+
export declare const param: (name: QueryParam | FragmentParam) => string;
|
|
102
|
+
export declare const RESERVED_URL_PARAMS: {
|
|
103
|
+
readonly query: readonly ["snag_t", "snag_off", "snag_debug", "snag_theme"];
|
|
104
|
+
readonly fragment: readonly ["snag_k"];
|
|
105
|
+
};
|
|
106
|
+
/** Point consts for hot paths: a chunk needing ONE key imports it and
|
|
107
|
+
* drops the whole table — object properties cannot tree-shake, and the
|
|
108
|
+
* panels chunk was carrying ten keys to read two (M2-13). */
|
|
109
|
+
export declare const DISCLOSURE_KEY = "snag:disclosure:v1";
|
|
110
|
+
export declare const IDENTITY_KEY = "snag:identity";
|
|
111
|
+
export declare const THEME_ATTR = "data-snag-theme";
|
|
112
|
+
/** Web-storage keys (§2.6). Parameterised keys are functions. */
|
|
113
|
+
export declare const storageKey: {
|
|
114
|
+
readonly session: (uid: string) => string;
|
|
115
|
+
readonly identity: "snag:identity";
|
|
116
|
+
readonly threads: (uid: string, env: string) => string;
|
|
117
|
+
readonly counts: (origin: string) => string;
|
|
118
|
+
readonly draft: (route: string, draftId: string) => string;
|
|
119
|
+
readonly outbox: (uid: string) => string;
|
|
120
|
+
readonly mode: "snag:mode";
|
|
121
|
+
readonly handoff: "snag:handoff";
|
|
122
|
+
readonly seenCoach: "snag:seen-coach";
|
|
123
|
+
readonly disclosure: "snag:disclosure:v1";
|
|
124
|
+
};
|
|
125
|
+
/** The v0.1 IndexedDB blob store (§2.6, 05 §10.1) — one database, one
|
|
126
|
+
* object store, shared by screenshots, reviewer images and the v2 outbox. */
|
|
127
|
+
export declare const IDB: {
|
|
128
|
+
readonly database: "snag:blobs";
|
|
129
|
+
readonly store: "blobs";
|
|
130
|
+
};
|
|
131
|
+
/** Anchored regex sources matching every registered key — AC-22's storage diff. */
|
|
132
|
+
export declare const STORAGE_KEY_PATTERNS: readonly ["^snag:session:.+$", "^snag:identity$", "^snag:threads:.+:.+$", "^snag:counts:.+$", "^snag:draft:.+:.+$", "^snag:outbox:.+$", "^snag:mode$", "^snag:handoff$", "^snag:seen-coach$", "^snag:disclosure:v1$"];
|
|
133
|
+
/** The WIDGET's two (§2.7). Neither carries a credential, and no cookie authenticates the widget API — it runs on somebody else's origin. */
|
|
134
|
+
export declare const COOKIE: {
|
|
135
|
+
readonly off: "snag_off";
|
|
136
|
+
readonly on: "snag_on";
|
|
137
|
+
};
|
|
138
|
+
/** The dashboard's session + CSRF pair (05 §4.7) — first-party, on the server's own origin, owner audience. */
|
|
139
|
+
export declare const DASHBOARD_COOKIE: {
|
|
140
|
+
readonly session: "snag_session";
|
|
141
|
+
readonly csrf: "snag_csrf";
|
|
142
|
+
};
|
|
143
|
+
/** 05 §4.7 names these two constants directly; both spellings resolve to the same string. */
|
|
144
|
+
export declare const DASHBOARD_SESSION_COOKIE: "snag_session";
|
|
145
|
+
export declare const DASHBOARD_CSRF_COOKIE: "snag_csrf";
|
|
146
|
+
/** Built widget chunks (§2.8). Preact lives only in `panels`. */
|
|
147
|
+
export declare const ARTIFACT: {
|
|
148
|
+
readonly stub: "snag.js";
|
|
149
|
+
readonly runtime: "snag.runtime.js";
|
|
150
|
+
readonly panels: "snag.panels.js";
|
|
151
|
+
readonly crumbs: "snag.crumbs.js";
|
|
152
|
+
readonly capture: "snag.capture.js";
|
|
153
|
+
readonly screenshot: "snag.screenshot.js";
|
|
154
|
+
readonly anchorExt: "snag.anchor-ext.js";
|
|
155
|
+
readonly anchorFuzzy: "snag.anchor-fuzzy.js";
|
|
156
|
+
readonly dock: "snag.dock.js";
|
|
157
|
+
readonly text: "snag.text.js";
|
|
158
|
+
};
|
|
159
|
+
/** Wire-form entity id prefixes (§2.10). Not brand-derived. */
|
|
160
|
+
export declare const ID_PREFIX: {
|
|
161
|
+
readonly project: "prj_";
|
|
162
|
+
readonly thread: "thr_";
|
|
163
|
+
readonly comment: "cmt_";
|
|
164
|
+
readonly reviewer: "rev_";
|
|
165
|
+
readonly attachment: "att_";
|
|
166
|
+
readonly link: "lnk_";
|
|
167
|
+
readonly round: "rnd_";
|
|
168
|
+
};
|
|
169
|
+
/** Credential literals (07 §8.2). Brand-DERIVED, and deliberately not the three-letter row shape: the literal is what gets registered with secret scanning, so a token pasted into a public repo is reported. */
|
|
170
|
+
export declare const CREDENTIAL_PREFIX: {
|
|
171
|
+
readonly mcp: "snagmcp_";
|
|
172
|
+
};
|
|
173
|
+
/** CustomEvents dispatched on document (§2.11). */
|
|
174
|
+
export declare const DOM_EVENT: {
|
|
175
|
+
readonly ready: "snagset:ready";
|
|
176
|
+
readonly activate: "snagset:activate";
|
|
177
|
+
readonly deactivate: "snagset:deactivate";
|
|
178
|
+
readonly threadCreated: "snagset:thread-created";
|
|
179
|
+
readonly threadResolved: "snagset:thread-resolved";
|
|
180
|
+
};
|
|
181
|
+
/** Request/response header names inside §2.1's prefix (05 §2.3). */
|
|
182
|
+
export declare const HTTP_HEADER: {
|
|
183
|
+
readonly client: "x-snagset-client";
|
|
184
|
+
readonly schema: "x-snagset-schema";
|
|
185
|
+
readonly idempotencyKey: "x-snagset-idempotency-key";
|
|
186
|
+
readonly identity: "x-snagset-identity";
|
|
187
|
+
readonly csrf: "x-snagset-csrf";
|
|
188
|
+
readonly attachmentKind: "x-snagset-attachment-kind";
|
|
189
|
+
readonly attachmentWidth: "x-snagset-attachment-width";
|
|
190
|
+
readonly attachmentHeight: "x-snagset-attachment-height";
|
|
191
|
+
readonly server: "x-snagset-server";
|
|
192
|
+
readonly minClient: "x-snagset-min-client";
|
|
193
|
+
readonly time: "x-snagset-time";
|
|
194
|
+
readonly configVersion: "x-snagset-config-version";
|
|
195
|
+
readonly idempotentReplay: "x-snagset-idempotent-replay";
|
|
196
|
+
readonly requestId: "x-snagset-request-id";
|
|
197
|
+
};
|
|
198
|
+
/** Host-authored `<meta name>` keys we READ and never mint (§2.11). 03 §5.3: `route` is the only one. */
|
|
199
|
+
export declare const META_NAME: {
|
|
200
|
+
readonly route: "snagset:route";
|
|
201
|
+
};
|
|
202
|
+
/** Keys for Symbol.for() patch bookkeeping (§2.11). */
|
|
203
|
+
export declare const SYMBOL_KEY: {
|
|
204
|
+
readonly original: "snagset.original";
|
|
205
|
+
readonly wrapped: "snagset.wrapped";
|
|
206
|
+
readonly instance: "snagset.instance";
|
|
207
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// GENERATED FILE — DO NOT EDIT BY HAND.
|
|
3
|
+
// Source of truth: packages/types/registry.json
|
|
4
|
+
// Regenerate: pnpm gen:names (CI runs `pnpm gen:names:check`)
|
|
5
|
+
//
|
|
6
|
+
// The Name Registry (specs/00-foundations.md §2) made executable. Every token
|
|
7
|
+
// below is DERIVED from registry.json's `brand` block — the product name lives
|
|
8
|
+
// in exactly one place. test/rename.test.ts proves it: flip the brand, and no
|
|
9
|
+
// token of the old one survives. SCHEMA_VERSION is the one deliberate literal.
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
/** The one place the product name exists. Everything else derives from this. */
|
|
12
|
+
export const BRAND = {
|
|
13
|
+
$comment: "The ONLY place the product name appears. `long` = display/global name. `short` = the terse prefix that goes on attributes, classes, params, storage keys and artefact filenames (a client's DOM should not carry a long word). `pkg` = the npm/lowercase form used for env vars, headers, metrics, DOM events and symbols.",
|
|
14
|
+
long: "Snagset",
|
|
15
|
+
short: "snag",
|
|
16
|
+
pkg: "snagset",
|
|
17
|
+
};
|
|
18
|
+
export const PRODUCT = "Snagset";
|
|
19
|
+
export const NPM_PACKAGE = "snagset";
|
|
20
|
+
export const NPM_SCOPE = "@snagset";
|
|
21
|
+
/** Frozen once v1 stores real data — stamped into exports, issue bodies and the MCP contract, so it must survive a rename (registry.frozen). */
|
|
22
|
+
export const SCHEMA_VERSION = "snagset.v1";
|
|
23
|
+
export const CSS_VAR_PREFIX = "--snag-";
|
|
24
|
+
export const HTTP_HEADER_PREFIX = "x-snagset-";
|
|
25
|
+
export const ENV_PREFIX = "SNAGSET_";
|
|
26
|
+
export const PG_TYPE_PREFIX = "snag_";
|
|
27
|
+
export const METRIC_PREFIX = "snagset_";
|
|
28
|
+
export const DOM_EVENT_PREFIX = "snagset:";
|
|
29
|
+
export const SYMBOL_PREFIX = "snagset.";
|
|
30
|
+
export const MENTION_SCHEME = "snag:user/";
|
|
31
|
+
export const SKIP_HEADER = "x-snagset-skip";
|
|
32
|
+
export const THREAD_REF_PREFIX = "SN-";
|
|
33
|
+
export const TERM_SET = "Snagset";
|
|
34
|
+
export const TERM_SET_PLURAL = "Snagsets";
|
|
35
|
+
export const TERM_ITEM = "Snag";
|
|
36
|
+
export const TERM_ITEM_PLURAL = "Snags";
|
|
37
|
+
export const RESERVED_CUSTOM_PREFIX = "cr.";
|
|
38
|
+
/** @returns e.g. `x-snagset-idempotency-key` */
|
|
39
|
+
export const header = (name) => HTTP_HEADER_PREFIX + name;
|
|
40
|
+
/** @returns e.g. `SNAGSET_SESSION_TTL_S` */
|
|
41
|
+
export const env = (name) => ENV_PREFIX + name;
|
|
42
|
+
/** @returns e.g. `snag_attachment_kind` */
|
|
43
|
+
export const pgType = (name) => PG_TYPE_PREFIX + name;
|
|
44
|
+
/** @returns e.g. `snagset_http_requests_total` */
|
|
45
|
+
export const metric = (name) => METRIC_PREFIX + name;
|
|
46
|
+
/** window.* names (§2.2). Dunders are internal/test affordances, not public API. */
|
|
47
|
+
export const GLOBALS = {
|
|
48
|
+
api: "Snagset",
|
|
49
|
+
config: "SnagsetConfig",
|
|
50
|
+
disabled: "__SNAGSET_DISABLED__",
|
|
51
|
+
assetBase: "__SNAGSET_ASSET_BASE__",
|
|
52
|
+
collisionFallback: "__snagset__",
|
|
53
|
+
};
|
|
54
|
+
const scriptAttrToken = {
|
|
55
|
+
site: "data-snag-site",
|
|
56
|
+
host: "data-snag-host",
|
|
57
|
+
disabled: "data-snag-disabled",
|
|
58
|
+
env: "data-snag-env",
|
|
59
|
+
mode: "data-snag-mode",
|
|
60
|
+
badge: "data-snag-badge",
|
|
61
|
+
lang: "data-snag-lang",
|
|
62
|
+
theme: "data-snag-theme",
|
|
63
|
+
position: "data-snag-position",
|
|
64
|
+
offset: "data-snag-offset",
|
|
65
|
+
assetBase: "data-snag-asset-base",
|
|
66
|
+
branch: "data-snag-branch",
|
|
67
|
+
build: "data-snag-build",
|
|
68
|
+
storage: "data-snag-storage",
|
|
69
|
+
hotkey: "data-snag-hotkey",
|
|
70
|
+
allowAutomation: "data-snag-allow-automation",
|
|
71
|
+
screenshots: "data-snag-screenshots",
|
|
72
|
+
maskSelectors: "data-snag-mask-selectors",
|
|
73
|
+
nonce: "data-snag-nonce",
|
|
74
|
+
autoMount: "data-snag-auto-mount",
|
|
75
|
+
};
|
|
76
|
+
export const scriptAttr = (name) => scriptAttrToken[name];
|
|
77
|
+
const widgetAttrToken = {
|
|
78
|
+
root: "data-snag-root",
|
|
79
|
+
};
|
|
80
|
+
export const widgetAttr = (name) => widgetAttrToken[name];
|
|
81
|
+
const hostAttrToken = {
|
|
82
|
+
anchor: "data-snag-anchor",
|
|
83
|
+
ignore: "data-snag-ignore",
|
|
84
|
+
mask: "data-snag-mask",
|
|
85
|
+
nomask: "data-snag-nomask",
|
|
86
|
+
src: "data-snag-src",
|
|
87
|
+
component: "data-snag-component",
|
|
88
|
+
};
|
|
89
|
+
export const hostAttr = (name) => hostAttrToken[name];
|
|
90
|
+
const cssClassToken = {
|
|
91
|
+
mask: "snag-mask",
|
|
92
|
+
block: "snag-block",
|
|
93
|
+
};
|
|
94
|
+
export const cssClass = (name) => cssClassToken[name];
|
|
95
|
+
/** Custom properties (§2.1's prefix, enumerated). 06 §14 makes every one of these public SemVer surface, so they are registered rather than minted at the point of use. Values are NOT here — the registry governs names, never design. */
|
|
96
|
+
export const CSS_VAR = {
|
|
97
|
+
font: "--snag-font",
|
|
98
|
+
accent: "--snag-accent",
|
|
99
|
+
onAccent: "--snag-onAccent",
|
|
100
|
+
bg: "--snag-bg",
|
|
101
|
+
fg: "--snag-fg",
|
|
102
|
+
muted: "--snag-muted",
|
|
103
|
+
border: "--snag-border",
|
|
104
|
+
radius: "--snag-radius",
|
|
105
|
+
shadow: "--snag-shadow",
|
|
106
|
+
z: "--snag-z",
|
|
107
|
+
motion: "--snag-motion",
|
|
108
|
+
surface: "--snag-surface",
|
|
109
|
+
sunken: "--snag-sunken",
|
|
110
|
+
hover: "--snag-hover",
|
|
111
|
+
focus: "--snag-focus",
|
|
112
|
+
disabled: "--snag-disabled",
|
|
113
|
+
statusOpen: "--snag-status-open",
|
|
114
|
+
statusProgress: "--snag-status-progress",
|
|
115
|
+
statusReview: "--snag-status-review",
|
|
116
|
+
statusResolved: "--snag-status-resolved",
|
|
117
|
+
anchorOrphan: "--snag-anchor-orphan",
|
|
118
|
+
tbH: "--snag-tb-h",
|
|
119
|
+
dockW: "--snag-dock-w",
|
|
120
|
+
kb: "--snag-kb",
|
|
121
|
+
thumbX: "--snag-thumb-x",
|
|
122
|
+
thumbW: "--snag-thumb-w",
|
|
123
|
+
};
|
|
124
|
+
/** Which tokens belong to which role — the dark block must override theme members only. */
|
|
125
|
+
export const CSS_VAR_GROUPS = {
|
|
126
|
+
theme: ["font", "accent", "onAccent", "bg", "fg", "muted", "border", "radius", "shadow", "z", "motion"],
|
|
127
|
+
derived: ["surface", "sunken", "hover", "focus", "disabled"],
|
|
128
|
+
status: ["statusOpen", "statusProgress", "statusReview", "statusResolved"],
|
|
129
|
+
anchor: ["anchorOrphan"],
|
|
130
|
+
geometry: ["tbH", "dockW", "kb", "thumbX", "thumbW"],
|
|
131
|
+
};
|
|
132
|
+
/** @returns e.g. `--snag-bg` */
|
|
133
|
+
export const cssVar = (name) => CSS_VAR[name];
|
|
134
|
+
/** @returns e.g. `var(--snag-bg)` — what stylesheets actually interpolate. */
|
|
135
|
+
export const cssRef = (name) => `var(${CSS_VAR[name]})`;
|
|
136
|
+
/** @returns e.g. `snag_t`. The review-link secret is a FRAGMENT param (§2.5). */
|
|
137
|
+
export const param = (name) => "snag_" + name;
|
|
138
|
+
export const RESERVED_URL_PARAMS = {
|
|
139
|
+
query: ["snag_t", "snag_off", "snag_debug", "snag_theme"],
|
|
140
|
+
fragment: ["snag_k"],
|
|
141
|
+
};
|
|
142
|
+
/** Point consts for hot paths: a chunk needing ONE key imports it and
|
|
143
|
+
* drops the whole table — object properties cannot tree-shake, and the
|
|
144
|
+
* panels chunk was carrying ten keys to read two (M2-13). */
|
|
145
|
+
export const DISCLOSURE_KEY = "snag:disclosure:v1";
|
|
146
|
+
export const IDENTITY_KEY = "snag:identity";
|
|
147
|
+
export const THEME_ATTR = "data-snag-theme";
|
|
148
|
+
/** Web-storage keys (§2.6). Parameterised keys are functions. */
|
|
149
|
+
export const storageKey = {
|
|
150
|
+
session: (uid) => `snag:session:${uid}`,
|
|
151
|
+
identity: IDENTITY_KEY,
|
|
152
|
+
threads: (uid, env) => `snag:threads:${uid}:${env}`,
|
|
153
|
+
counts: (origin) => `snag:counts:${origin}`,
|
|
154
|
+
draft: (route, draftId) => `snag:draft:${route}:${draftId}`,
|
|
155
|
+
outbox: (uid) => `snag:outbox:${uid}`,
|
|
156
|
+
mode: "snag:mode",
|
|
157
|
+
handoff: "snag:handoff",
|
|
158
|
+
seenCoach: "snag:seen-coach",
|
|
159
|
+
disclosure: DISCLOSURE_KEY,
|
|
160
|
+
};
|
|
161
|
+
/** The v0.1 IndexedDB blob store (§2.6, 05 §10.1) — one database, one
|
|
162
|
+
* object store, shared by screenshots, reviewer images and the v2 outbox. */
|
|
163
|
+
export const IDB = {
|
|
164
|
+
database: "snag:blobs",
|
|
165
|
+
store: "blobs",
|
|
166
|
+
};
|
|
167
|
+
/** Anchored regex sources matching every registered key — AC-22's storage diff. */
|
|
168
|
+
export const STORAGE_KEY_PATTERNS = [
|
|
169
|
+
"^snag:session:.+$",
|
|
170
|
+
"^snag:identity$",
|
|
171
|
+
"^snag:threads:.+:.+$",
|
|
172
|
+
"^snag:counts:.+$",
|
|
173
|
+
"^snag:draft:.+:.+$",
|
|
174
|
+
"^snag:outbox:.+$",
|
|
175
|
+
"^snag:mode$",
|
|
176
|
+
"^snag:handoff$",
|
|
177
|
+
"^snag:seen-coach$",
|
|
178
|
+
"^snag:disclosure:v1$",
|
|
179
|
+
];
|
|
180
|
+
/** The WIDGET's two (§2.7). Neither carries a credential, and no cookie authenticates the widget API — it runs on somebody else's origin. */
|
|
181
|
+
export const COOKIE = {
|
|
182
|
+
off: "snag_off",
|
|
183
|
+
on: "snag_on",
|
|
184
|
+
};
|
|
185
|
+
/** The dashboard's session + CSRF pair (05 §4.7) — first-party, on the server's own origin, owner audience. */
|
|
186
|
+
export const DASHBOARD_COOKIE = {
|
|
187
|
+
session: "snag_session",
|
|
188
|
+
csrf: "snag_csrf",
|
|
189
|
+
};
|
|
190
|
+
/** 05 §4.7 names these two constants directly; both spellings resolve to the same string. */
|
|
191
|
+
export const DASHBOARD_SESSION_COOKIE = DASHBOARD_COOKIE.session;
|
|
192
|
+
export const DASHBOARD_CSRF_COOKIE = DASHBOARD_COOKIE.csrf;
|
|
193
|
+
/** Built widget chunks (§2.8). Preact lives only in `panels`. */
|
|
194
|
+
export const ARTIFACT = {
|
|
195
|
+
stub: "snag.js",
|
|
196
|
+
runtime: "snag.runtime.js",
|
|
197
|
+
panels: "snag.panels.js",
|
|
198
|
+
crumbs: "snag.crumbs.js",
|
|
199
|
+
capture: "snag.capture.js",
|
|
200
|
+
screenshot: "snag.screenshot.js",
|
|
201
|
+
anchorExt: "snag.anchor-ext.js",
|
|
202
|
+
anchorFuzzy: "snag.anchor-fuzzy.js",
|
|
203
|
+
dock: "snag.dock.js",
|
|
204
|
+
text: "snag.text.js",
|
|
205
|
+
};
|
|
206
|
+
/** Wire-form entity id prefixes (§2.10). Not brand-derived. */
|
|
207
|
+
export const ID_PREFIX = {
|
|
208
|
+
project: "prj_",
|
|
209
|
+
thread: "thr_",
|
|
210
|
+
comment: "cmt_",
|
|
211
|
+
reviewer: "rev_",
|
|
212
|
+
attachment: "att_",
|
|
213
|
+
link: "lnk_",
|
|
214
|
+
round: "rnd_",
|
|
215
|
+
};
|
|
216
|
+
/** Credential literals (07 §8.2). Brand-DERIVED, and deliberately not the three-letter row shape: the literal is what gets registered with secret scanning, so a token pasted into a public repo is reported. */
|
|
217
|
+
export const CREDENTIAL_PREFIX = {
|
|
218
|
+
mcp: "snagmcp_",
|
|
219
|
+
};
|
|
220
|
+
/** CustomEvents dispatched on document (§2.11). */
|
|
221
|
+
export const DOM_EVENT = {
|
|
222
|
+
ready: "snagset:ready",
|
|
223
|
+
activate: "snagset:activate",
|
|
224
|
+
deactivate: "snagset:deactivate",
|
|
225
|
+
threadCreated: "snagset:thread-created",
|
|
226
|
+
threadResolved: "snagset:thread-resolved",
|
|
227
|
+
};
|
|
228
|
+
/** Request/response header names inside §2.1's prefix (05 §2.3). */
|
|
229
|
+
export const HTTP_HEADER = {
|
|
230
|
+
client: "x-snagset-client",
|
|
231
|
+
schema: "x-snagset-schema",
|
|
232
|
+
idempotencyKey: "x-snagset-idempotency-key",
|
|
233
|
+
identity: "x-snagset-identity",
|
|
234
|
+
csrf: "x-snagset-csrf",
|
|
235
|
+
attachmentKind: "x-snagset-attachment-kind",
|
|
236
|
+
attachmentWidth: "x-snagset-attachment-width",
|
|
237
|
+
attachmentHeight: "x-snagset-attachment-height",
|
|
238
|
+
server: "x-snagset-server",
|
|
239
|
+
minClient: "x-snagset-min-client",
|
|
240
|
+
time: "x-snagset-time",
|
|
241
|
+
configVersion: "x-snagset-config-version",
|
|
242
|
+
idempotentReplay: "x-snagset-idempotent-replay",
|
|
243
|
+
requestId: "x-snagset-request-id",
|
|
244
|
+
};
|
|
245
|
+
/** Host-authored `<meta name>` keys we READ and never mint (§2.11). 03 §5.3: `route` is the only one. */
|
|
246
|
+
export const META_NAME = {
|
|
247
|
+
route: "snagset:route",
|
|
248
|
+
};
|
|
249
|
+
/** Keys for Symbol.for() patch bookkeeping (§2.11). */
|
|
250
|
+
export const SYMBOL_KEY = {
|
|
251
|
+
original: "snagset.original",
|
|
252
|
+
wrapped: "snagset.wrapped",
|
|
253
|
+
instance: "snagset.instance",
|
|
254
|
+
};
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type InitOptions } from "./index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset/vite` — D67, the config-level install.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // vite.config.ts
|
|
7
|
+
* import { snagset } from "snagset/vite";
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [snagset({ host: "https://review.example.com", site: "prj_…" })],
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* One line in a file the project already has, and no component in the tree at
|
|
15
|
+
* all. This is the piece of Vercel's install story worth taking (their
|
|
16
|
+
* SvelteKit path is `vercelToolbar()` in `vite.config`) — and because Nuxt,
|
|
17
|
+
* SvelteKit, Astro, SolidStart and Qwik all sit on Vite, this single plugin is
|
|
18
|
+
* the config-level answer for every one of them: put it under `vite.plugins`
|
|
19
|
+
* in `nuxt.config`, or beside `sveltekit()`.
|
|
20
|
+
*
|
|
21
|
+
* ── WHY THERE IS NO `vite` IMPORT IN THIS FILE ──
|
|
22
|
+
*
|
|
23
|
+
* The plugin is a plain object — a `name` and a `transformIndexHtml` hook —
|
|
24
|
+
* and Vite's contract for it is structural. Importing `vite` for its `Plugin`
|
|
25
|
+
* type would make a types-only package a peer dependency and pin us to its
|
|
26
|
+
* version churn, to say nothing this file's shape does not already say. The
|
|
27
|
+
* local types below are the subset we implement, and they assign cleanly to
|
|
28
|
+
* every Vite since the hook stabilized (v2).
|
|
29
|
+
*
|
|
30
|
+
* ── DEV AND BUILD BOTH, DELIBERATELY ──
|
|
31
|
+
*
|
|
32
|
+
* The tag is safe in a production build because who sees the toolbar is a
|
|
33
|
+
* DASHBOARD setting, checked by the stub on load (D66) — shipping the tag is
|
|
34
|
+
* not showing the toolbar. So the plugin does not carry an `enabled`/`dev`
|
|
35
|
+
* option that re-answers a question Vite already answers natively: a project
|
|
36
|
+
* that wants the tag only in dev writes the standard idiom
|
|
37
|
+
* `mode === "development" ? snagset({…}) : undefined` in its own config.
|
|
38
|
+
*/
|
|
39
|
+
/** The subset of Vite's `HtmlTagDescriptor` this plugin produces. */
|
|
40
|
+
export interface SnagsetHtmlTag {
|
|
41
|
+
tag: string;
|
|
42
|
+
attrs: Record<string, string | boolean>;
|
|
43
|
+
injectTo: "body";
|
|
44
|
+
}
|
|
45
|
+
/** The subset of Vite's `Plugin` this plugin is. */
|
|
46
|
+
export interface SnagsetVitePlugin {
|
|
47
|
+
name: string;
|
|
48
|
+
transformIndexHtml: () => SnagsetHtmlTag[];
|
|
49
|
+
}
|
|
50
|
+
export declare function snagset(options: InitOptions): SnagsetVitePlugin;
|
|
51
|
+
export default snagset;
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { tagFor } from "./index.js";
|
|
2
|
+
export function snagset(options) {
|
|
3
|
+
// Fail in the factory, not in the hook: a config error should stop `vite`
|
|
4
|
+
// at startup with a sentence, never surface mid-build as a mangled page.
|
|
5
|
+
if (!options?.host)
|
|
6
|
+
throw new Error("snagset/vite: `host` is required — the URL of your Snagset instance.");
|
|
7
|
+
if (!options.site)
|
|
8
|
+
throw new Error("snagset/vite: `site` is required — the project uid, which looks like prj_….");
|
|
9
|
+
const { src, attrs } = tagFor(options);
|
|
10
|
+
return {
|
|
11
|
+
name: "snagset",
|
|
12
|
+
transformIndexHtml() {
|
|
13
|
+
// `injectTo: "body"` is the docs' own instruction — "immediately before
|
|
14
|
+
// the closing </body> tag" — expressed as the hook's vocabulary.
|
|
15
|
+
return [{ tag: "script", attrs: { src, defer: true, ...attrs }, injectTo: "body" }];
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export default snagset;
|
|
20
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,YAAY,CAAC;AAqDtD,MAAM,UAAU,OAAO,CAAC,OAAoB;IAC1C,0EAA0E;IAC1E,yEAAyE;IACzE,IAAI,CAAC,OAAO,EAAE,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC5G,IAAI,CAAC,OAAO,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IAElH,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,kBAAkB;YAChB,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACtF,CAAC;KACF,CAAC;AACJ,CAAC;AAED,eAAe,OAAO,CAAC"}
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type PropType } from "vue";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset/vue` — D63.
|
|
4
|
+
*
|
|
5
|
+
* ```vue
|
|
6
|
+
* <script setup>
|
|
7
|
+
* import { Snagset } from "snagset/vue";
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <template>
|
|
11
|
+
* <Snagset host="https://review.example.com" site="prj_…" />
|
|
12
|
+
* </template>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Same shape as the React one and for the same reason: renders nothing, mounts
|
|
16
|
+
* on mount, tears down on unmount. `vue` is an optional peer dependency.
|
|
17
|
+
*
|
|
18
|
+
* Written with `defineComponent` and a render function rather than as an SFC
|
|
19
|
+
* because an SFC needs `@vitejs/plugin-vue` at BUILD time — in this package's
|
|
20
|
+
* build, not the consumer's. A component that renders null does not need a
|
|
21
|
+
* template compiler, and adding one would make the Vue subpath the only part
|
|
22
|
+
* of this package that cannot be built by `tsc`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const Snagset: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
25
|
+
host: {
|
|
26
|
+
type: StringConstructor;
|
|
27
|
+
required: true;
|
|
28
|
+
};
|
|
29
|
+
site: {
|
|
30
|
+
type: StringConstructor;
|
|
31
|
+
required: true;
|
|
32
|
+
};
|
|
33
|
+
env: StringConstructor;
|
|
34
|
+
mode: StringConstructor;
|
|
35
|
+
theme: StringConstructor;
|
|
36
|
+
lang: StringConstructor;
|
|
37
|
+
position: StringConstructor;
|
|
38
|
+
hotkey: StringConstructor;
|
|
39
|
+
branch: StringConstructor;
|
|
40
|
+
build: StringConstructor;
|
|
41
|
+
storage: StringConstructor;
|
|
42
|
+
badge: PropType<string>;
|
|
43
|
+
autoMount: PropType<string>;
|
|
44
|
+
}>, () => null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
45
|
+
host: {
|
|
46
|
+
type: StringConstructor;
|
|
47
|
+
required: true;
|
|
48
|
+
};
|
|
49
|
+
site: {
|
|
50
|
+
type: StringConstructor;
|
|
51
|
+
required: true;
|
|
52
|
+
};
|
|
53
|
+
env: StringConstructor;
|
|
54
|
+
mode: StringConstructor;
|
|
55
|
+
theme: StringConstructor;
|
|
56
|
+
lang: StringConstructor;
|
|
57
|
+
position: StringConstructor;
|
|
58
|
+
hotkey: StringConstructor;
|
|
59
|
+
branch: StringConstructor;
|
|
60
|
+
build: StringConstructor;
|
|
61
|
+
storage: StringConstructor;
|
|
62
|
+
badge: PropType<string>;
|
|
63
|
+
autoMount: PropType<string>;
|
|
64
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
65
|
+
export default Snagset;
|