@pinagent/react-native 0.1.2 → 0.2.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/dist/babel.cjs CHANGED
@@ -120,7 +120,7 @@ function pinagentSource(babel) {
120
120
  const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];
121
121
  const comp = enclosingComponentName(path, t);
122
122
  if (comp) attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));
123
- node.attributes.push(...attrs);
123
+ node.attributes.unshift(...attrs);
124
124
  } }
125
125
  };
126
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"babel.cjs","names":["sep"],"sources":["../src/babel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n/**\n * Metro/Babel source-tagging plugin — the React Native analog of the web\n * `@pinagent/babel-plugin`.\n *\n * It splices a `data-pa-loc=\"<file>:<line>:<col>\"` prop (plus a\n * `data-pa-comp=\"<EnclosingComponent>\"` companion) onto every authored JSX\n * element, exactly mirroring what the web babel plugin emits as a DOM\n * attribute. On React Native that prop survives onto the host fiber's\n * `memoizedProps`, so {@link resolvePick} can read it back at tap time.\n *\n * ## Why this exists (and didn't used to)\n *\n * The original RN design leaned on each fiber's `_debugSource`, populated in\n * dev by `@babel/plugin-transform-react-jsx-source` — \"reuse RN's, no custom\n * plugin needed\". **React 19 removed `_debugSource`** (the `ReactElement`\n * constructor no longer takes a `source` arg; the `__source` prop is consumed\n * by `jsxDEV` and never reaches `memoizedProps`), and **RN 0.81+ dropped the\n * `source` field from `getInspectorDataForViewAtPoint`**. So the runtime no\n * longer carries any source location — we have to inject our own, at build\n * time, the same way web does.\n *\n * Wire it into `babel.config.js` (dev only) BEFORE `babel-preset-expo`'s JSX\n * transform so the attribute is present when JSX lowers to `jsxDEV`:\n *\n * const pinagentSource = require('@pinagent/react-native/babel').default;\n * module.exports = (api) => {\n * api.cache(true);\n * const dev = process.env.NODE_ENV !== 'production';\n * return {\n * presets: ['babel-preset-expo'],\n * plugins: dev ? [pinagentSource] : [],\n * };\n * };\n *\n * Typed loosely (no `@babel/*` type deps) on purpose — like {@link inspector},\n * this is a thin, version-tolerant shim over an external toolchain that this\n * otherwise web-only monorepo doesn't carry types for.\n */\nimport { isAbsolute, relative, sep } from 'node:path';\n\n/** The attribute the web plugin emits — reused verbatim so reads match. */\nconst ATTR = 'data-pa-loc';\n/** Companion attribute carrying the enclosing component name. */\nconst COMP_ATTR = 'data-pa-comp';\n\n// Minimal structural typing for the slice of Babel we touch. Babel hands the\n// plugin a `types` namespace and node paths; we lean on duck-typing rather\n// than pulling in @types/babel__core.\n// biome-ignore lint/suspicious/noExplicitAny: external babel AST, typed loosely\ntype Any = any;\n\ninterface PluginState {\n filename?: string;\n cwd?: string;\n opts?: { projectRoot?: string };\n file?: { opts?: { filename?: string; cwd?: string; root?: string } };\n}\n\nfunction toPosix(p: string): string {\n return sep === '/' ? p : p.split(sep).join('/');\n}\n\n/**\n * Walk up to the nearest enclosing React component — the closest\n * function/class ancestor with a PascalCase name. Lowercase callbacks\n * (`items.map(x => <Row/>)`) are skipped, so list items report the component\n * that owns the list. Mirrors `@pinagent/babel-plugin`'s `transform.ts`.\n */\nfunction enclosingComponentName(path: Any, t: Any): string | null {\n let fn = path.getFunctionParent?.();\n while (fn) {\n const name = inferFunctionName(fn, t);\n if (name && /^[A-Z]/.test(name)) return name;\n fn = fn.getFunctionParent?.();\n }\n return null;\n}\n\nfunction inferFunctionName(fnPath: Any, t: Any): string | null {\n const node = fnPath.node;\n if (t.isFunctionDeclaration(node) && node.id) return node.id.name;\n if (t.isClassMethod(node) || t.isClassPrivateMethod(node)) {\n const cls = fnPath.findParent((p: Any) => p.isClassDeclaration() || p.isClassExpression());\n if (cls) {\n const clsNode = cls.node;\n if ((t.isClassDeclaration(clsNode) || t.isClassExpression(clsNode)) && clsNode.id) {\n return clsNode.id.name;\n }\n return nameFromBinding(cls, t);\n }\n return null;\n }\n return nameFromBinding(fnPath, t);\n}\n\nfunction nameFromBinding(p: Any, t: Any): string | null {\n const pn = p.parentPath?.node;\n if (!pn) return null;\n if (t.isVariableDeclarator(pn) && t.isIdentifier(pn.id)) return pn.id.name;\n if ((t.isObjectProperty(pn) || t.isObjectMethod(pn)) && t.isIdentifier(pn.key))\n return pn.key.name;\n if ((t.isClassProperty(pn) || t.isClassMethod(pn)) && t.isIdentifier(pn.key)) return pn.key.name;\n if (t.isAssignmentExpression(pn) && t.isIdentifier(pn.left)) return pn.left.name;\n return null;\n}\n\nfunction isFragment(name: Any, t: Any): boolean {\n if (t.isJSXIdentifier(name)) return name.name === 'Fragment';\n if (t.isJSXMemberExpression(name)) {\n return t.isJSXIdentifier(name.property) && name.property.name === 'Fragment';\n }\n return t.isJSXNamespacedName(name);\n}\n\n/** Resolve the project root used to make paths relative. */\nfunction rootFor(state: PluginState): string | undefined {\n return state.opts?.projectRoot ?? state.file?.opts?.root ?? state.cwd ?? state.file?.opts?.cwd;\n}\n\n/** Resolve the file being transformed. */\nfunction filenameFor(state: PluginState): string | undefined {\n return state.filename ?? state.file?.opts?.filename;\n}\n\nexport interface PinagentBabelOptions {\n /** Project root for project-relative paths. Defaults to Babel's cwd/root. */\n projectRoot?: string;\n}\n\n/**\n * The Babel plugin. Default export so a `babel.config.js` can `require()` it\n * and drop the function straight into `plugins`.\n */\nexport default function pinagentSource(babel: { types: Any }): Any {\n const t = babel.types;\n return {\n name: 'pinagent-source',\n visitor: {\n JSXOpeningElement(path: Any, state: PluginState) {\n const node = path.node;\n if (isFragment(node.name, t)) return;\n\n // Already tagged? Idempotent — re-running is a no-op.\n const tagged = node.attributes.some(\n (a: Any) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === ATTR,\n );\n if (tagged) return;\n\n const loc = node.loc?.start;\n if (!loc) return;\n\n const filename = filenameFor(state);\n if (!filename || filename.includes(`${sep}node_modules${sep}`)) return;\n\n const root = rootFor(state);\n let rel = root && isAbsolute(filename) ? relative(root, filename) : filename;\n rel = toPosix(rel);\n // Only tag files inside the project root — skip anything resolved\n // outside it (e.g. the in-tree widget source under an out-of-root\n // package, which the developer never taps on).\n if (rel.startsWith('../')) return;\n\n const value = `${rel}:${loc.line}:${loc.column + 1}`;\n const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];\n const comp = enclosingComponentName(path, t);\n if (comp) {\n attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));\n }\n node.attributes.push(...attrs);\n },\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"babel.cjs","names":["sep"],"sources":["../src/babel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n/**\n * Metro/Babel source-tagging plugin — the React Native analog of the web\n * `@pinagent/babel-plugin`.\n *\n * It splices a `data-pa-loc=\"<file>:<line>:<col>\"` prop (plus a\n * `data-pa-comp=\"<EnclosingComponent>\"` companion) onto every authored JSX\n * element, exactly mirroring what the web babel plugin emits as a DOM\n * attribute. On React Native that prop survives onto the host fiber's\n * `memoizedProps`, so {@link resolvePick} can read it back at tap time.\n *\n * ## Why this exists (and didn't used to)\n *\n * The original RN design leaned on each fiber's `_debugSource`, populated in\n * dev by `@babel/plugin-transform-react-jsx-source` — \"reuse RN's, no custom\n * plugin needed\". **React 19 removed `_debugSource`** (the `ReactElement`\n * constructor no longer takes a `source` arg; the `__source` prop is consumed\n * by `jsxDEV` and never reaches `memoizedProps`), and **RN 0.81+ dropped the\n * `source` field from `getInspectorDataForViewAtPoint`**. So the runtime no\n * longer carries any source location — we have to inject our own, at build\n * time, the same way web does.\n *\n * Wire it into `babel.config.js` (dev only) BEFORE `babel-preset-expo`'s JSX\n * transform so the attribute is present when JSX lowers to `jsxDEV`:\n *\n * const pinagentSource = require('@pinagent/react-native/babel').default;\n * module.exports = (api) => {\n * api.cache(true);\n * const dev = process.env.NODE_ENV !== 'production';\n * return {\n * presets: ['babel-preset-expo'],\n * plugins: dev ? [pinagentSource] : [],\n * };\n * };\n *\n * Typed loosely (no `@babel/*` type deps) on purpose — like {@link inspector},\n * this is a thin, version-tolerant shim over an external toolchain that this\n * otherwise web-only monorepo doesn't carry types for.\n */\nimport { isAbsolute, relative, sep } from 'node:path';\n\n/** The attribute the web plugin emits — reused verbatim so reads match. */\nconst ATTR = 'data-pa-loc';\n/** Companion attribute carrying the enclosing component name. */\nconst COMP_ATTR = 'data-pa-comp';\n\n// Minimal structural typing for the slice of Babel we touch. Babel hands the\n// plugin a `types` namespace and node paths; we lean on duck-typing rather\n// than pulling in @types/babel__core.\n// biome-ignore lint/suspicious/noExplicitAny: external babel AST, typed loosely\ntype Any = any;\n\ninterface PluginState {\n filename?: string;\n cwd?: string;\n opts?: { projectRoot?: string };\n file?: { opts?: { filename?: string; cwd?: string; root?: string } };\n}\n\nfunction toPosix(p: string): string {\n return sep === '/' ? p : p.split(sep).join('/');\n}\n\n/**\n * Walk up to the nearest enclosing React component — the closest\n * function/class ancestor with a PascalCase name. Lowercase callbacks\n * (`items.map(x => <Row/>)`) are skipped, so list items report the component\n * that owns the list. Mirrors `@pinagent/babel-plugin`'s `transform.ts`.\n */\nfunction enclosingComponentName(path: Any, t: Any): string | null {\n let fn = path.getFunctionParent?.();\n while (fn) {\n const name = inferFunctionName(fn, t);\n if (name && /^[A-Z]/.test(name)) return name;\n fn = fn.getFunctionParent?.();\n }\n return null;\n}\n\nfunction inferFunctionName(fnPath: Any, t: Any): string | null {\n const node = fnPath.node;\n if (t.isFunctionDeclaration(node) && node.id) return node.id.name;\n if (t.isClassMethod(node) || t.isClassPrivateMethod(node)) {\n const cls = fnPath.findParent((p: Any) => p.isClassDeclaration() || p.isClassExpression());\n if (cls) {\n const clsNode = cls.node;\n if ((t.isClassDeclaration(clsNode) || t.isClassExpression(clsNode)) && clsNode.id) {\n return clsNode.id.name;\n }\n return nameFromBinding(cls, t);\n }\n return null;\n }\n return nameFromBinding(fnPath, t);\n}\n\nfunction nameFromBinding(p: Any, t: Any): string | null {\n const pn = p.parentPath?.node;\n if (!pn) return null;\n if (t.isVariableDeclarator(pn) && t.isIdentifier(pn.id)) return pn.id.name;\n if ((t.isObjectProperty(pn) || t.isObjectMethod(pn)) && t.isIdentifier(pn.key))\n return pn.key.name;\n if ((t.isClassProperty(pn) || t.isClassMethod(pn)) && t.isIdentifier(pn.key)) return pn.key.name;\n if (t.isAssignmentExpression(pn) && t.isIdentifier(pn.left)) return pn.left.name;\n return null;\n}\n\nfunction isFragment(name: Any, t: Any): boolean {\n if (t.isJSXIdentifier(name)) return name.name === 'Fragment';\n if (t.isJSXMemberExpression(name)) {\n return t.isJSXIdentifier(name.property) && name.property.name === 'Fragment';\n }\n return t.isJSXNamespacedName(name);\n}\n\n/** Resolve the project root used to make paths relative. */\nfunction rootFor(state: PluginState): string | undefined {\n return state.opts?.projectRoot ?? state.file?.opts?.root ?? state.cwd ?? state.file?.opts?.cwd;\n}\n\n/** Resolve the file being transformed. */\nfunction filenameFor(state: PluginState): string | undefined {\n return state.filename ?? state.file?.opts?.filename;\n}\n\nexport interface PinagentBabelOptions {\n /** Project root for project-relative paths. Defaults to Babel's cwd/root. */\n projectRoot?: string;\n}\n\n/**\n * The Babel plugin. Default export so a `babel.config.js` can `require()` it\n * and drop the function straight into `plugins`.\n */\nexport default function pinagentSource(babel: { types: Any }): Any {\n const t = babel.types;\n return {\n name: 'pinagent-source',\n visitor: {\n JSXOpeningElement(path: Any, state: PluginState) {\n const node = path.node;\n if (isFragment(node.name, t)) return;\n\n // Already tagged? Idempotent — re-running is a no-op.\n const tagged = node.attributes.some(\n (a: Any) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === ATTR,\n );\n if (tagged) return;\n\n const loc = node.loc?.start;\n if (!loc) return;\n\n const filename = filenameFor(state);\n if (!filename || filename.includes(`${sep}node_modules${sep}`)) return;\n\n const root = rootFor(state);\n let rel = root && isAbsolute(filename) ? relative(root, filename) : filename;\n rel = toPosix(rel);\n // Only tag files inside the project root — skip anything resolved\n // outside it (e.g. the in-tree widget source under an out-of-root\n // package, which the developer never taps on).\n if (rel.startsWith('../')) return;\n\n const value = `${rel}:${loc.line}:${loc.column + 1}`;\n const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];\n const comp = enclosingComponentName(path, t);\n if (comp) {\n attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));\n }\n // Prepend, NOT append — this is load-bearing for generic wrapper\n // components. A wrapper like `<ViewRn {...rest} />` forwards the\n // call site's own `data-pa-loc` (which arrives via `rest`) onto the\n // host view. JSX props resolve last-wins, so our spliced attribute\n // must come BEFORE `{...rest}` to let the forwarded call-site location\n // override the wrapper's own definition line. Append it after the\n // spread and every element rendered through the wrapper collapses to\n // the wrapper's `file:line`, and the tapped child becomes unreachable.\n // The web `@pinagent/babel-plugin` gets this for free by inserting at\n // `name.end` (before all attributes); we mirror that here.\n node.attributes.unshift(...attrs);\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAM,OAAO;;AAEb,MAAM,YAAY;AAelB,SAAS,QAAQ,GAAmB;CAClC,OAAOA,UAAAA,QAAQ,MAAM,IAAI,EAAE,MAAMA,UAAAA,GAAG,EAAE,KAAK,GAAG;AAChD;;;;;;;AAQA,SAAS,uBAAuB,MAAW,GAAuB;CAChE,IAAI,KAAK,KAAK,oBAAoB;CAClC,OAAO,IAAI;EACT,MAAM,OAAO,kBAAkB,IAAI,CAAC;EACpC,IAAI,QAAQ,SAAS,KAAK,IAAI,GAAG,OAAO;EACxC,KAAK,GAAG,oBAAoB;CAC9B;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,QAAa,GAAuB;CAC7D,MAAM,OAAO,OAAO;CACpB,IAAI,EAAE,sBAAsB,IAAI,KAAK,KAAK,IAAI,OAAO,KAAK,GAAG;CAC7D,IAAI,EAAE,cAAc,IAAI,KAAK,EAAE,qBAAqB,IAAI,GAAG;EACzD,MAAM,MAAM,OAAO,YAAY,MAAW,EAAE,mBAAmB,KAAK,EAAE,kBAAkB,CAAC;EACzF,IAAI,KAAK;GACP,MAAM,UAAU,IAAI;GACpB,KAAK,EAAE,mBAAmB,OAAO,KAAK,EAAE,kBAAkB,OAAO,MAAM,QAAQ,IAC7E,OAAO,QAAQ,GAAG;GAEpB,OAAO,gBAAgB,KAAK,CAAC;EAC/B;EACA,OAAO;CACT;CACA,OAAO,gBAAgB,QAAQ,CAAC;AAClC;AAEA,SAAS,gBAAgB,GAAQ,GAAuB;CACtD,MAAM,KAAK,EAAE,YAAY;CACzB,IAAI,CAAC,IAAI,OAAO;CAChB,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,aAAa,GAAG,EAAE,GAAG,OAAO,GAAG,GAAG;CACtE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,GAAG,GAAG,GAC3E,OAAO,GAAG,IAAI;CAChB,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,GAAG,GAAG,GAAG,OAAO,GAAG,IAAI;CAC5F,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK;CAC5E,OAAO;AACT;AAEA,SAAS,WAAW,MAAW,GAAiB;CAC9C,IAAI,EAAE,gBAAgB,IAAI,GAAG,OAAO,KAAK,SAAS;CAClD,IAAI,EAAE,sBAAsB,IAAI,GAC9B,OAAO,EAAE,gBAAgB,KAAK,QAAQ,KAAK,KAAK,SAAS,SAAS;CAEpE,OAAO,EAAE,oBAAoB,IAAI;AACnC;;AAGA,SAAS,QAAQ,OAAwC;CACvD,OAAO,MAAM,MAAM,eAAe,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,MAAM,MAAM,MAAM;AAC7F;;AAGA,SAAS,YAAY,OAAwC;CAC3D,OAAO,MAAM,YAAY,MAAM,MAAM,MAAM;AAC7C;;;;;AAWA,SAAwB,eAAe,OAA4B;CACjE,MAAM,IAAI,MAAM;CAChB,OAAO;EACL,MAAM;EACN,SAAS,EACP,kBAAkB,MAAW,OAAoB;GAC/C,MAAM,OAAO,KAAK;GAClB,IAAI,WAAW,KAAK,MAAM,CAAC,GAAG;GAM9B,IAHe,KAAK,WAAW,MAC5B,MAAW,EAAE,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,KAAK,EAAE,KAAK,SAAS,IAEzE,GAAG;GAEZ,MAAM,MAAM,KAAK,KAAK;GACtB,IAAI,CAAC,KAAK;GAEV,MAAM,WAAW,YAAY,KAAK;GAClC,IAAI,CAAC,YAAY,SAAS,SAAS,GAAGA,UAAAA,IAAI,cAAcA,UAAAA,KAAK,GAAG;GAEhE,MAAM,OAAO,QAAQ,KAAK;GAC1B,IAAI,MAAM,SAAA,GAAA,UAAA,YAAmB,QAAQ,KAAA,GAAA,UAAA,UAAa,MAAM,QAAQ,IAAI;GACpE,MAAM,QAAQ,GAAG;GAIjB,IAAI,IAAI,WAAW,KAAK,GAAG;GAE3B,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,KAAK,GAAG,IAAI,SAAS;GACjD,MAAM,QAAQ,CAAC,EAAE,aAAa,EAAE,cAAc,IAAI,GAAG,EAAE,cAAc,KAAK,CAAC,CAAC;GAC5E,MAAM,OAAO,uBAAuB,MAAM,CAAC;GAC3C,IAAI,MACF,MAAM,KAAK,EAAE,aAAa,EAAE,cAAc,SAAS,GAAG,EAAE,cAAc,IAAI,CAAC,CAAC;GAY9E,KAAK,WAAW,QAAQ,GAAG,KAAK;EAClC,EACF;CACF;AACF"}
package/dist/babel.js CHANGED
@@ -120,7 +120,7 @@ function pinagentSource(babel) {
120
120
  const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];
121
121
  const comp = enclosingComponentName(path, t);
122
122
  if (comp) attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));
123
- node.attributes.push(...attrs);
123
+ node.attributes.unshift(...attrs);
124
124
  } }
125
125
  };
126
126
  }
package/dist/babel.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"babel.js","names":[],"sources":["../src/babel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n/**\n * Metro/Babel source-tagging plugin — the React Native analog of the web\n * `@pinagent/babel-plugin`.\n *\n * It splices a `data-pa-loc=\"<file>:<line>:<col>\"` prop (plus a\n * `data-pa-comp=\"<EnclosingComponent>\"` companion) onto every authored JSX\n * element, exactly mirroring what the web babel plugin emits as a DOM\n * attribute. On React Native that prop survives onto the host fiber's\n * `memoizedProps`, so {@link resolvePick} can read it back at tap time.\n *\n * ## Why this exists (and didn't used to)\n *\n * The original RN design leaned on each fiber's `_debugSource`, populated in\n * dev by `@babel/plugin-transform-react-jsx-source` — \"reuse RN's, no custom\n * plugin needed\". **React 19 removed `_debugSource`** (the `ReactElement`\n * constructor no longer takes a `source` arg; the `__source` prop is consumed\n * by `jsxDEV` and never reaches `memoizedProps`), and **RN 0.81+ dropped the\n * `source` field from `getInspectorDataForViewAtPoint`**. So the runtime no\n * longer carries any source location — we have to inject our own, at build\n * time, the same way web does.\n *\n * Wire it into `babel.config.js` (dev only) BEFORE `babel-preset-expo`'s JSX\n * transform so the attribute is present when JSX lowers to `jsxDEV`:\n *\n * const pinagentSource = require('@pinagent/react-native/babel').default;\n * module.exports = (api) => {\n * api.cache(true);\n * const dev = process.env.NODE_ENV !== 'production';\n * return {\n * presets: ['babel-preset-expo'],\n * plugins: dev ? [pinagentSource] : [],\n * };\n * };\n *\n * Typed loosely (no `@babel/*` type deps) on purpose — like {@link inspector},\n * this is a thin, version-tolerant shim over an external toolchain that this\n * otherwise web-only monorepo doesn't carry types for.\n */\nimport { isAbsolute, relative, sep } from 'node:path';\n\n/** The attribute the web plugin emits — reused verbatim so reads match. */\nconst ATTR = 'data-pa-loc';\n/** Companion attribute carrying the enclosing component name. */\nconst COMP_ATTR = 'data-pa-comp';\n\n// Minimal structural typing for the slice of Babel we touch. Babel hands the\n// plugin a `types` namespace and node paths; we lean on duck-typing rather\n// than pulling in @types/babel__core.\n// biome-ignore lint/suspicious/noExplicitAny: external babel AST, typed loosely\ntype Any = any;\n\ninterface PluginState {\n filename?: string;\n cwd?: string;\n opts?: { projectRoot?: string };\n file?: { opts?: { filename?: string; cwd?: string; root?: string } };\n}\n\nfunction toPosix(p: string): string {\n return sep === '/' ? p : p.split(sep).join('/');\n}\n\n/**\n * Walk up to the nearest enclosing React component — the closest\n * function/class ancestor with a PascalCase name. Lowercase callbacks\n * (`items.map(x => <Row/>)`) are skipped, so list items report the component\n * that owns the list. Mirrors `@pinagent/babel-plugin`'s `transform.ts`.\n */\nfunction enclosingComponentName(path: Any, t: Any): string | null {\n let fn = path.getFunctionParent?.();\n while (fn) {\n const name = inferFunctionName(fn, t);\n if (name && /^[A-Z]/.test(name)) return name;\n fn = fn.getFunctionParent?.();\n }\n return null;\n}\n\nfunction inferFunctionName(fnPath: Any, t: Any): string | null {\n const node = fnPath.node;\n if (t.isFunctionDeclaration(node) && node.id) return node.id.name;\n if (t.isClassMethod(node) || t.isClassPrivateMethod(node)) {\n const cls = fnPath.findParent((p: Any) => p.isClassDeclaration() || p.isClassExpression());\n if (cls) {\n const clsNode = cls.node;\n if ((t.isClassDeclaration(clsNode) || t.isClassExpression(clsNode)) && clsNode.id) {\n return clsNode.id.name;\n }\n return nameFromBinding(cls, t);\n }\n return null;\n }\n return nameFromBinding(fnPath, t);\n}\n\nfunction nameFromBinding(p: Any, t: Any): string | null {\n const pn = p.parentPath?.node;\n if (!pn) return null;\n if (t.isVariableDeclarator(pn) && t.isIdentifier(pn.id)) return pn.id.name;\n if ((t.isObjectProperty(pn) || t.isObjectMethod(pn)) && t.isIdentifier(pn.key))\n return pn.key.name;\n if ((t.isClassProperty(pn) || t.isClassMethod(pn)) && t.isIdentifier(pn.key)) return pn.key.name;\n if (t.isAssignmentExpression(pn) && t.isIdentifier(pn.left)) return pn.left.name;\n return null;\n}\n\nfunction isFragment(name: Any, t: Any): boolean {\n if (t.isJSXIdentifier(name)) return name.name === 'Fragment';\n if (t.isJSXMemberExpression(name)) {\n return t.isJSXIdentifier(name.property) && name.property.name === 'Fragment';\n }\n return t.isJSXNamespacedName(name);\n}\n\n/** Resolve the project root used to make paths relative. */\nfunction rootFor(state: PluginState): string | undefined {\n return state.opts?.projectRoot ?? state.file?.opts?.root ?? state.cwd ?? state.file?.opts?.cwd;\n}\n\n/** Resolve the file being transformed. */\nfunction filenameFor(state: PluginState): string | undefined {\n return state.filename ?? state.file?.opts?.filename;\n}\n\nexport interface PinagentBabelOptions {\n /** Project root for project-relative paths. Defaults to Babel's cwd/root. */\n projectRoot?: string;\n}\n\n/**\n * The Babel plugin. Default export so a `babel.config.js` can `require()` it\n * and drop the function straight into `plugins`.\n */\nexport default function pinagentSource(babel: { types: Any }): Any {\n const t = babel.types;\n return {\n name: 'pinagent-source',\n visitor: {\n JSXOpeningElement(path: Any, state: PluginState) {\n const node = path.node;\n if (isFragment(node.name, t)) return;\n\n // Already tagged? Idempotent — re-running is a no-op.\n const tagged = node.attributes.some(\n (a: Any) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === ATTR,\n );\n if (tagged) return;\n\n const loc = node.loc?.start;\n if (!loc) return;\n\n const filename = filenameFor(state);\n if (!filename || filename.includes(`${sep}node_modules${sep}`)) return;\n\n const root = rootFor(state);\n let rel = root && isAbsolute(filename) ? relative(root, filename) : filename;\n rel = toPosix(rel);\n // Only tag files inside the project root — skip anything resolved\n // outside it (e.g. the in-tree widget source under an out-of-root\n // package, which the developer never taps on).\n if (rel.startsWith('../')) return;\n\n const value = `${rel}:${loc.line}:${loc.column + 1}`;\n const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];\n const comp = enclosingComponentName(path, t);\n if (comp) {\n attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));\n }\n node.attributes.push(...attrs);\n },\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"babel.js","names":[],"sources":["../src/babel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n/**\n * Metro/Babel source-tagging plugin — the React Native analog of the web\n * `@pinagent/babel-plugin`.\n *\n * It splices a `data-pa-loc=\"<file>:<line>:<col>\"` prop (plus a\n * `data-pa-comp=\"<EnclosingComponent>\"` companion) onto every authored JSX\n * element, exactly mirroring what the web babel plugin emits as a DOM\n * attribute. On React Native that prop survives onto the host fiber's\n * `memoizedProps`, so {@link resolvePick} can read it back at tap time.\n *\n * ## Why this exists (and didn't used to)\n *\n * The original RN design leaned on each fiber's `_debugSource`, populated in\n * dev by `@babel/plugin-transform-react-jsx-source` — \"reuse RN's, no custom\n * plugin needed\". **React 19 removed `_debugSource`** (the `ReactElement`\n * constructor no longer takes a `source` arg; the `__source` prop is consumed\n * by `jsxDEV` and never reaches `memoizedProps`), and **RN 0.81+ dropped the\n * `source` field from `getInspectorDataForViewAtPoint`**. So the runtime no\n * longer carries any source location — we have to inject our own, at build\n * time, the same way web does.\n *\n * Wire it into `babel.config.js` (dev only) BEFORE `babel-preset-expo`'s JSX\n * transform so the attribute is present when JSX lowers to `jsxDEV`:\n *\n * const pinagentSource = require('@pinagent/react-native/babel').default;\n * module.exports = (api) => {\n * api.cache(true);\n * const dev = process.env.NODE_ENV !== 'production';\n * return {\n * presets: ['babel-preset-expo'],\n * plugins: dev ? [pinagentSource] : [],\n * };\n * };\n *\n * Typed loosely (no `@babel/*` type deps) on purpose — like {@link inspector},\n * this is a thin, version-tolerant shim over an external toolchain that this\n * otherwise web-only monorepo doesn't carry types for.\n */\nimport { isAbsolute, relative, sep } from 'node:path';\n\n/** The attribute the web plugin emits — reused verbatim so reads match. */\nconst ATTR = 'data-pa-loc';\n/** Companion attribute carrying the enclosing component name. */\nconst COMP_ATTR = 'data-pa-comp';\n\n// Minimal structural typing for the slice of Babel we touch. Babel hands the\n// plugin a `types` namespace and node paths; we lean on duck-typing rather\n// than pulling in @types/babel__core.\n// biome-ignore lint/suspicious/noExplicitAny: external babel AST, typed loosely\ntype Any = any;\n\ninterface PluginState {\n filename?: string;\n cwd?: string;\n opts?: { projectRoot?: string };\n file?: { opts?: { filename?: string; cwd?: string; root?: string } };\n}\n\nfunction toPosix(p: string): string {\n return sep === '/' ? p : p.split(sep).join('/');\n}\n\n/**\n * Walk up to the nearest enclosing React component — the closest\n * function/class ancestor with a PascalCase name. Lowercase callbacks\n * (`items.map(x => <Row/>)`) are skipped, so list items report the component\n * that owns the list. Mirrors `@pinagent/babel-plugin`'s `transform.ts`.\n */\nfunction enclosingComponentName(path: Any, t: Any): string | null {\n let fn = path.getFunctionParent?.();\n while (fn) {\n const name = inferFunctionName(fn, t);\n if (name && /^[A-Z]/.test(name)) return name;\n fn = fn.getFunctionParent?.();\n }\n return null;\n}\n\nfunction inferFunctionName(fnPath: Any, t: Any): string | null {\n const node = fnPath.node;\n if (t.isFunctionDeclaration(node) && node.id) return node.id.name;\n if (t.isClassMethod(node) || t.isClassPrivateMethod(node)) {\n const cls = fnPath.findParent((p: Any) => p.isClassDeclaration() || p.isClassExpression());\n if (cls) {\n const clsNode = cls.node;\n if ((t.isClassDeclaration(clsNode) || t.isClassExpression(clsNode)) && clsNode.id) {\n return clsNode.id.name;\n }\n return nameFromBinding(cls, t);\n }\n return null;\n }\n return nameFromBinding(fnPath, t);\n}\n\nfunction nameFromBinding(p: Any, t: Any): string | null {\n const pn = p.parentPath?.node;\n if (!pn) return null;\n if (t.isVariableDeclarator(pn) && t.isIdentifier(pn.id)) return pn.id.name;\n if ((t.isObjectProperty(pn) || t.isObjectMethod(pn)) && t.isIdentifier(pn.key))\n return pn.key.name;\n if ((t.isClassProperty(pn) || t.isClassMethod(pn)) && t.isIdentifier(pn.key)) return pn.key.name;\n if (t.isAssignmentExpression(pn) && t.isIdentifier(pn.left)) return pn.left.name;\n return null;\n}\n\nfunction isFragment(name: Any, t: Any): boolean {\n if (t.isJSXIdentifier(name)) return name.name === 'Fragment';\n if (t.isJSXMemberExpression(name)) {\n return t.isJSXIdentifier(name.property) && name.property.name === 'Fragment';\n }\n return t.isJSXNamespacedName(name);\n}\n\n/** Resolve the project root used to make paths relative. */\nfunction rootFor(state: PluginState): string | undefined {\n return state.opts?.projectRoot ?? state.file?.opts?.root ?? state.cwd ?? state.file?.opts?.cwd;\n}\n\n/** Resolve the file being transformed. */\nfunction filenameFor(state: PluginState): string | undefined {\n return state.filename ?? state.file?.opts?.filename;\n}\n\nexport interface PinagentBabelOptions {\n /** Project root for project-relative paths. Defaults to Babel's cwd/root. */\n projectRoot?: string;\n}\n\n/**\n * The Babel plugin. Default export so a `babel.config.js` can `require()` it\n * and drop the function straight into `plugins`.\n */\nexport default function pinagentSource(babel: { types: Any }): Any {\n const t = babel.types;\n return {\n name: 'pinagent-source',\n visitor: {\n JSXOpeningElement(path: Any, state: PluginState) {\n const node = path.node;\n if (isFragment(node.name, t)) return;\n\n // Already tagged? Idempotent — re-running is a no-op.\n const tagged = node.attributes.some(\n (a: Any) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === ATTR,\n );\n if (tagged) return;\n\n const loc = node.loc?.start;\n if (!loc) return;\n\n const filename = filenameFor(state);\n if (!filename || filename.includes(`${sep}node_modules${sep}`)) return;\n\n const root = rootFor(state);\n let rel = root && isAbsolute(filename) ? relative(root, filename) : filename;\n rel = toPosix(rel);\n // Only tag files inside the project root — skip anything resolved\n // outside it (e.g. the in-tree widget source under an out-of-root\n // package, which the developer never taps on).\n if (rel.startsWith('../')) return;\n\n const value = `${rel}:${loc.line}:${loc.column + 1}`;\n const attrs = [t.jsxAttribute(t.jsxIdentifier(ATTR), t.stringLiteral(value))];\n const comp = enclosingComponentName(path, t);\n if (comp) {\n attrs.push(t.jsxAttribute(t.jsxIdentifier(COMP_ATTR), t.stringLiteral(comp)));\n }\n // Prepend, NOT append — this is load-bearing for generic wrapper\n // components. A wrapper like `<ViewRn {...rest} />` forwards the\n // call site's own `data-pa-loc` (which arrives via `rest`) onto the\n // host view. JSX props resolve last-wins, so our spliced attribute\n // must come BEFORE `{...rest}` to let the forwarded call-site location\n // override the wrapper's own definition line. Append it after the\n // spread and every element rendered through the wrapper collapses to\n // the wrapper's `file:line`, and the tapped child becomes unreachable.\n // The web `@pinagent/babel-plugin` gets this for free by inserting at\n // `name.end` (before all attributes); we mirror that here.\n node.attributes.unshift(...attrs);\n },\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ CREATE TABLE `active_runs` (
2
+ `conversation_id` text PRIMARY KEY NOT NULL,
3
+ `started_at` integer NOT NULL,
4
+ `current_turn` integer NOT NULL,
5
+ `awaiting_ask_id` text,
6
+ `last_error` text,
7
+ FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade
8
+ );
9
+ --> statement-breakpoint
10
+ CREATE TABLE `conversations` (
11
+ `id` text PRIMARY KEY NOT NULL,
12
+ `comment` text NOT NULL,
13
+ `agent_session_id` text,
14
+ `status` text DEFAULT 'pending' NOT NULL,
15
+ `note` text,
16
+ `commit_sha` text,
17
+ `branch` text,
18
+ `worktree_path` text,
19
+ `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
20
+ `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
21
+ `resolved_at` integer
22
+ );
23
+ --> statement-breakpoint
24
+ CREATE TABLE `messages` (
25
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
26
+ `conversation_id` text NOT NULL,
27
+ `turn` integer NOT NULL,
28
+ `role` text NOT NULL,
29
+ `content` text NOT NULL,
30
+ `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
31
+ FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade
32
+ );
33
+ --> statement-breakpoint
34
+ CREATE TABLE `widget_anchors` (
35
+ `conversation_id` text PRIMARY KEY NOT NULL,
36
+ `url` text NOT NULL,
37
+ `file` text,
38
+ `line` integer,
39
+ `col` integer,
40
+ `selector` text NOT NULL,
41
+ `click_x` integer,
42
+ `click_y` integer,
43
+ `viewport_w` integer,
44
+ `viewport_h` integer,
45
+ FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE cascade
46
+ );
@@ -0,0 +1 @@
1
+ ALTER TABLE `widget_anchors` ADD `user_agent` text;
@@ -0,0 +1,13 @@
1
+ ALTER TABLE `conversations` ADD `worktree_state` text DEFAULT 'none' NOT NULL;
2
+ --> statement-breakpoint
3
+ -- Backfill rows that already have a worktree on disk so the UI sees them as
4
+ -- actionable (Land/Discard) rather than as inline-mode rows.
5
+ UPDATE `conversations`
6
+ SET `worktree_state` = 'active'
7
+ WHERE `worktree_path` IS NOT NULL AND `commit_sha` IS NULL;
8
+ --> statement-breakpoint
9
+ -- Older rows whose worktrees were already merged externally (commit_sha set)
10
+ -- are treated as `landed` so they don't reappear in the lifecycle UI.
11
+ UPDATE `conversations`
12
+ SET `worktree_state` = 'landed'
13
+ WHERE `worktree_path` IS NOT NULL AND `commit_sha` IS NOT NULL;
@@ -0,0 +1,13 @@
1
+ CREATE TABLE `pull_requests` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `number` integer NOT NULL,
4
+ `url` text NOT NULL,
5
+ `branch` text NOT NULL,
6
+ `base_branch` text NOT NULL,
7
+ `title` text NOT NULL,
8
+ `body` text DEFAULT '' NOT NULL,
9
+ `state` text DEFAULT 'open' NOT NULL,
10
+ `conversation_ids` text DEFAULT ('[]') NOT NULL,
11
+ `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
12
+ `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL
13
+ );
@@ -0,0 +1,8 @@
1
+ CREATE TABLE `audit_events` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `conversation_id` text,
4
+ `actor` text NOT NULL,
5
+ `action` text NOT NULL,
6
+ `payload` text DEFAULT ('{}') NOT NULL,
7
+ `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL
8
+ );
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `conversations` ADD `title` text;--> statement-breakpoint
2
+ ALTER TABLE `conversations` ADD `archived` integer DEFAULT false NOT NULL;
@@ -0,0 +1,7 @@
1
+ UPDATE `conversations`
2
+ SET `worktree_state` = 'landed'
3
+ WHERE `status` = 'fixed' AND `worktree_state` = 'none';
4
+ --> statement-breakpoint
5
+ UPDATE `conversations`
6
+ SET `worktree_state` = 'discarded'
7
+ WHERE `status` = 'wontfix' AND `worktree_state` = 'none';
@@ -0,0 +1 @@
1
+ ALTER TABLE `widget_anchors` ADD `additional_anchors` text;
@@ -0,0 +1,5 @@
1
+ ALTER TABLE `widget_anchors` ADD `component` text;--> statement-breakpoint
2
+ ALTER TABLE `widget_anchors` ADD `component_path` text;--> statement-breakpoint
3
+ ALTER TABLE `widget_anchors` ADD `instance_index` integer;--> statement-breakpoint
4
+ ALTER TABLE `widget_anchors` ADD `instance_total` integer;--> statement-breakpoint
5
+ ALTER TABLE `widget_anchors` ADD `instance_fingerprint` text;
@@ -0,0 +1,328 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "b40e99e7-5146-4202-abfd-1c03df81d9f5",
5
+ "prevId": "00000000-0000-0000-0000-000000000000",
6
+ "tables": {
7
+ "active_runs": {
8
+ "name": "active_runs",
9
+ "columns": {
10
+ "conversation_id": {
11
+ "name": "conversation_id",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "started_at": {
18
+ "name": "started_at",
19
+ "type": "integer",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "current_turn": {
25
+ "name": "current_turn",
26
+ "type": "integer",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "awaiting_ask_id": {
32
+ "name": "awaiting_ask_id",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "last_error": {
39
+ "name": "last_error",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ }
45
+ },
46
+ "indexes": {},
47
+ "foreignKeys": {
48
+ "active_runs_conversation_id_conversations_id_fk": {
49
+ "name": "active_runs_conversation_id_conversations_id_fk",
50
+ "tableFrom": "active_runs",
51
+ "tableTo": "conversations",
52
+ "columnsFrom": [
53
+ "conversation_id"
54
+ ],
55
+ "columnsTo": [
56
+ "id"
57
+ ],
58
+ "onDelete": "cascade",
59
+ "onUpdate": "no action"
60
+ }
61
+ },
62
+ "compositePrimaryKeys": {},
63
+ "uniqueConstraints": {},
64
+ "checkConstraints": {}
65
+ },
66
+ "conversations": {
67
+ "name": "conversations",
68
+ "columns": {
69
+ "id": {
70
+ "name": "id",
71
+ "type": "text",
72
+ "primaryKey": true,
73
+ "notNull": true,
74
+ "autoincrement": false
75
+ },
76
+ "comment": {
77
+ "name": "comment",
78
+ "type": "text",
79
+ "primaryKey": false,
80
+ "notNull": true,
81
+ "autoincrement": false
82
+ },
83
+ "agent_session_id": {
84
+ "name": "agent_session_id",
85
+ "type": "text",
86
+ "primaryKey": false,
87
+ "notNull": false,
88
+ "autoincrement": false
89
+ },
90
+ "status": {
91
+ "name": "status",
92
+ "type": "text",
93
+ "primaryKey": false,
94
+ "notNull": true,
95
+ "autoincrement": false,
96
+ "default": "'pending'"
97
+ },
98
+ "note": {
99
+ "name": "note",
100
+ "type": "text",
101
+ "primaryKey": false,
102
+ "notNull": false,
103
+ "autoincrement": false
104
+ },
105
+ "commit_sha": {
106
+ "name": "commit_sha",
107
+ "type": "text",
108
+ "primaryKey": false,
109
+ "notNull": false,
110
+ "autoincrement": false
111
+ },
112
+ "branch": {
113
+ "name": "branch",
114
+ "type": "text",
115
+ "primaryKey": false,
116
+ "notNull": false,
117
+ "autoincrement": false
118
+ },
119
+ "worktree_path": {
120
+ "name": "worktree_path",
121
+ "type": "text",
122
+ "primaryKey": false,
123
+ "notNull": false,
124
+ "autoincrement": false
125
+ },
126
+ "created_at": {
127
+ "name": "created_at",
128
+ "type": "integer",
129
+ "primaryKey": false,
130
+ "notNull": true,
131
+ "autoincrement": false,
132
+ "default": "(unixepoch() * 1000)"
133
+ },
134
+ "updated_at": {
135
+ "name": "updated_at",
136
+ "type": "integer",
137
+ "primaryKey": false,
138
+ "notNull": true,
139
+ "autoincrement": false,
140
+ "default": "(unixepoch() * 1000)"
141
+ },
142
+ "resolved_at": {
143
+ "name": "resolved_at",
144
+ "type": "integer",
145
+ "primaryKey": false,
146
+ "notNull": false,
147
+ "autoincrement": false
148
+ }
149
+ },
150
+ "indexes": {},
151
+ "foreignKeys": {},
152
+ "compositePrimaryKeys": {},
153
+ "uniqueConstraints": {},
154
+ "checkConstraints": {}
155
+ },
156
+ "messages": {
157
+ "name": "messages",
158
+ "columns": {
159
+ "id": {
160
+ "name": "id",
161
+ "type": "integer",
162
+ "primaryKey": true,
163
+ "notNull": true,
164
+ "autoincrement": true
165
+ },
166
+ "conversation_id": {
167
+ "name": "conversation_id",
168
+ "type": "text",
169
+ "primaryKey": false,
170
+ "notNull": true,
171
+ "autoincrement": false
172
+ },
173
+ "turn": {
174
+ "name": "turn",
175
+ "type": "integer",
176
+ "primaryKey": false,
177
+ "notNull": true,
178
+ "autoincrement": false
179
+ },
180
+ "role": {
181
+ "name": "role",
182
+ "type": "text",
183
+ "primaryKey": false,
184
+ "notNull": true,
185
+ "autoincrement": false
186
+ },
187
+ "content": {
188
+ "name": "content",
189
+ "type": "text",
190
+ "primaryKey": false,
191
+ "notNull": true,
192
+ "autoincrement": false
193
+ },
194
+ "created_at": {
195
+ "name": "created_at",
196
+ "type": "integer",
197
+ "primaryKey": false,
198
+ "notNull": true,
199
+ "autoincrement": false,
200
+ "default": "(unixepoch() * 1000)"
201
+ }
202
+ },
203
+ "indexes": {},
204
+ "foreignKeys": {
205
+ "messages_conversation_id_conversations_id_fk": {
206
+ "name": "messages_conversation_id_conversations_id_fk",
207
+ "tableFrom": "messages",
208
+ "tableTo": "conversations",
209
+ "columnsFrom": [
210
+ "conversation_id"
211
+ ],
212
+ "columnsTo": [
213
+ "id"
214
+ ],
215
+ "onDelete": "cascade",
216
+ "onUpdate": "no action"
217
+ }
218
+ },
219
+ "compositePrimaryKeys": {},
220
+ "uniqueConstraints": {},
221
+ "checkConstraints": {}
222
+ },
223
+ "widget_anchors": {
224
+ "name": "widget_anchors",
225
+ "columns": {
226
+ "conversation_id": {
227
+ "name": "conversation_id",
228
+ "type": "text",
229
+ "primaryKey": true,
230
+ "notNull": true,
231
+ "autoincrement": false
232
+ },
233
+ "url": {
234
+ "name": "url",
235
+ "type": "text",
236
+ "primaryKey": false,
237
+ "notNull": true,
238
+ "autoincrement": false
239
+ },
240
+ "file": {
241
+ "name": "file",
242
+ "type": "text",
243
+ "primaryKey": false,
244
+ "notNull": false,
245
+ "autoincrement": false
246
+ },
247
+ "line": {
248
+ "name": "line",
249
+ "type": "integer",
250
+ "primaryKey": false,
251
+ "notNull": false,
252
+ "autoincrement": false
253
+ },
254
+ "col": {
255
+ "name": "col",
256
+ "type": "integer",
257
+ "primaryKey": false,
258
+ "notNull": false,
259
+ "autoincrement": false
260
+ },
261
+ "selector": {
262
+ "name": "selector",
263
+ "type": "text",
264
+ "primaryKey": false,
265
+ "notNull": true,
266
+ "autoincrement": false
267
+ },
268
+ "click_x": {
269
+ "name": "click_x",
270
+ "type": "integer",
271
+ "primaryKey": false,
272
+ "notNull": false,
273
+ "autoincrement": false
274
+ },
275
+ "click_y": {
276
+ "name": "click_y",
277
+ "type": "integer",
278
+ "primaryKey": false,
279
+ "notNull": false,
280
+ "autoincrement": false
281
+ },
282
+ "viewport_w": {
283
+ "name": "viewport_w",
284
+ "type": "integer",
285
+ "primaryKey": false,
286
+ "notNull": false,
287
+ "autoincrement": false
288
+ },
289
+ "viewport_h": {
290
+ "name": "viewport_h",
291
+ "type": "integer",
292
+ "primaryKey": false,
293
+ "notNull": false,
294
+ "autoincrement": false
295
+ }
296
+ },
297
+ "indexes": {},
298
+ "foreignKeys": {
299
+ "widget_anchors_conversation_id_conversations_id_fk": {
300
+ "name": "widget_anchors_conversation_id_conversations_id_fk",
301
+ "tableFrom": "widget_anchors",
302
+ "tableTo": "conversations",
303
+ "columnsFrom": [
304
+ "conversation_id"
305
+ ],
306
+ "columnsTo": [
307
+ "id"
308
+ ],
309
+ "onDelete": "cascade",
310
+ "onUpdate": "no action"
311
+ }
312
+ },
313
+ "compositePrimaryKeys": {},
314
+ "uniqueConstraints": {},
315
+ "checkConstraints": {}
316
+ }
317
+ },
318
+ "views": {},
319
+ "enums": {},
320
+ "_meta": {
321
+ "schemas": {},
322
+ "tables": {},
323
+ "columns": {}
324
+ },
325
+ "internal": {
326
+ "indexes": {}
327
+ }
328
+ }