polen 0.10.0-next.13 → 0.10.0-next.14

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.
Files changed (104) hide show
  1. package/build/api/vite/plugins/build.d.ts.map +1 -1
  2. package/build/api/vite/plugins/build.js +11 -3
  3. package/build/api/vite/plugins/build.js.map +1 -1
  4. package/build/api/vite/plugins/core.d.ts.map +1 -1
  5. package/build/api/vite/plugins/core.js +12 -10
  6. package/build/api/vite/plugins/core.js.map +1 -1
  7. package/build/api/vite/plugins/pages.d.ts.map +1 -1
  8. package/build/api/vite/plugins/pages.js +6 -7
  9. package/build/api/vite/plugins/pages.js.map +1 -1
  10. package/build/api/vite/plugins/serve.d.ts.map +1 -1
  11. package/build/api/vite/plugins/serve.js +47 -7
  12. package/build/api/vite/plugins/serve.js.map +1 -1
  13. package/build/lib/file-router/diagnostic-reporter.js +2 -2
  14. package/build/lib/file-router/diagnostic-reporter.js.map +1 -1
  15. package/build/lib/graphql-document/components/GraphQLDocument.d.ts.map +1 -1
  16. package/build/lib/graphql-document/components/GraphQLDocument.js +23 -11
  17. package/build/lib/graphql-document/components/GraphQLDocument.js.map +1 -1
  18. package/build/lib/graphql-document/positioning-simple.d.ts +0 -5
  19. package/build/lib/graphql-document/positioning-simple.d.ts.map +1 -1
  20. package/build/lib/graphql-document/positioning-simple.js +78 -90
  21. package/build/lib/graphql-document/positioning-simple.js.map +1 -1
  22. package/build/lib/kit-temp.d.ts +103 -0
  23. package/build/lib/kit-temp.d.ts.map +1 -1
  24. package/build/lib/kit-temp.js +236 -2
  25. package/build/lib/kit-temp.js.map +1 -1
  26. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts +1 -8
  27. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts.map +1 -1
  28. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js +48 -53
  29. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js.map +1 -1
  30. package/build/package-paths.js +3 -3
  31. package/build/package-paths.js.map +1 -1
  32. package/build/template/components/Link.d.ts +1 -1
  33. package/build/template/components/Link.d.ts.map +1 -1
  34. package/build/template/components/Link.js +14 -5
  35. package/build/template/components/Link.js.map +1 -1
  36. package/build/template/components/content/GraphQLDocumentWithSchema.d.ts.map +1 -1
  37. package/build/template/components/content/GraphQLDocumentWithSchema.js +0 -3
  38. package/build/template/components/content/GraphQLDocumentWithSchema.js.map +1 -1
  39. package/build/template/components/content/GraphQLDocumentWrapper.d.ts.map +1 -1
  40. package/build/template/components/content/GraphQLDocumentWrapper.js +8 -7
  41. package/build/template/components/content/GraphQLDocumentWrapper.js.map +1 -1
  42. package/build/template/components/sidebar/SidebarItem.js +2 -2
  43. package/build/template/entry.client.d.ts.map +1 -1
  44. package/build/template/entry.client.js +0 -3
  45. package/build/template/entry.client.js.map +1 -1
  46. package/build/template/hooks/useClientOnly.d.ts +9 -0
  47. package/build/template/hooks/useClientOnly.d.ts.map +1 -0
  48. package/build/template/hooks/useClientOnly.js +16 -0
  49. package/build/template/hooks/useClientOnly.js.map +1 -0
  50. package/build/template/routes/root.d.ts.map +1 -1
  51. package/build/template/routes/root.js +2 -150
  52. package/build/template/routes/root.js.map +1 -1
  53. package/build/template/server/app.d.ts +8 -1
  54. package/build/template/server/app.d.ts.map +1 -1
  55. package/build/template/server/app.js +21 -21
  56. package/build/template/server/app.js.map +1 -1
  57. package/build/template/server/create-page-html-response.d.ts +7 -0
  58. package/build/template/server/create-page-html-response.d.ts.map +1 -0
  59. package/build/template/server/{render-page.js → create-page-html-response.js} +11 -16
  60. package/build/template/server/create-page-html-response.js.map +1 -0
  61. package/build/template/server/main.js +2 -1
  62. package/build/template/server/main.js.map +1 -1
  63. package/build/template/server/middleware/page.d.ts +4 -0
  64. package/build/template/server/middleware/page.d.ts.map +1 -0
  65. package/build/template/server/middleware/page.js +15 -0
  66. package/build/template/server/middleware/page.js.map +1 -0
  67. package/build/template/server/middleware/unsupported-assets.d.ts +10 -0
  68. package/build/template/server/middleware/unsupported-assets.d.ts.map +1 -0
  69. package/build/template/server/middleware/unsupported-assets.js +21 -0
  70. package/build/template/server/middleware/unsupported-assets.js.map +1 -0
  71. package/build/template/server/ssg/generate.d.ts.map +1 -1
  72. package/build/template/server/ssg/generate.js +33 -34
  73. package/build/template/server/ssg/generate.js.map +1 -1
  74. package/build/template/styles/code-block.css +218 -0
  75. package/package.json +3 -2
  76. package/src/api/singletons/markdown/markdown.test.ts +1 -1
  77. package/src/api/vite/plugins/build.ts +97 -89
  78. package/src/api/vite/plugins/core.ts +15 -10
  79. package/src/api/vite/plugins/pages.ts +9 -7
  80. package/src/api/vite/plugins/serve.ts +62 -9
  81. package/src/lib/file-router/diagnostic-reporter.ts +2 -2
  82. package/src/lib/graphql-document/components/GraphQLDocument.tsx +23 -11
  83. package/src/lib/graphql-document/positioning-simple.test.ts +18 -22
  84. package/src/lib/graphql-document/positioning-simple.ts +97 -108
  85. package/src/lib/kit-temp.test.ts +15 -3
  86. package/src/lib/kit-temp.ts +304 -4
  87. package/src/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.ts +52 -58
  88. package/src/package-paths.ts +3 -3
  89. package/src/template/components/Link.tsx +20 -12
  90. package/src/template/components/content/GraphQLDocumentWithSchema.tsx +0 -5
  91. package/src/template/components/content/GraphQLDocumentWrapper.tsx +14 -7
  92. package/src/template/components/sidebar/SidebarItem.tsx +2 -2
  93. package/src/template/entry.client.tsx +0 -3
  94. package/src/template/hooks/useClientOnly.ts +21 -0
  95. package/src/template/routes/root.tsx +0 -159
  96. package/src/template/server/app.ts +33 -23
  97. package/src/template/server/{render-page.tsx → create-page-html-response.ts} +19 -16
  98. package/src/template/server/main.ts +2 -1
  99. package/src/template/server/middleware/page.ts +19 -0
  100. package/src/template/server/middleware/unsupported-assets.ts +25 -0
  101. package/src/template/server/ssg/generate.ts +68 -72
  102. package/build/template/server/render-page.d.ts +0 -3
  103. package/build/template/server/render-page.d.ts.map +0 -1
  104. package/build/template/server/render-page.js.map +0 -1
@@ -1,29 +1,80 @@
1
1
  import type { Config } from '#api/config/index'
2
2
  import { reportError } from '#api/server/report-error'
3
- import type { Hono } from '#dep/hono/index'
3
+ import { Hono } from '#dep/hono/index'
4
4
  import type { Vite } from '#dep/vite/index'
5
5
  import { ResponseInternalServerError } from '#lib/kit-temp'
6
6
  import { debugPolen } from '#singletons/debug'
7
7
  import * as HonoNodeServer from '@hono/node-server'
8
- import { Err } from '@wollybeard/kit'
8
+ import { Err, Obj } from '@wollybeard/kit'
9
9
 
10
10
  type App = Hono.Hono
11
11
 
12
+ interface AppOptions {
13
+ hooks?: {
14
+ transformHtml?: Array<(html: string, ctx: Hono.Context) => Promise<string> | string>
15
+ }
16
+ }
17
+
12
18
  interface AppServerModule {
13
- app: App
19
+ createApp: (options: AppOptions) => App
14
20
  }
15
21
 
16
22
  export const Serve = (
17
23
  config: Config.Config,
18
24
  ): Vite.PluginOption => {
19
25
  const debug = debugPolen.sub(`serve`)
26
+ debug('construct')
27
+ const appModulePath = config.paths.framework.template.server.app
28
+
20
29
  let appPromise: Promise<App | Error>
21
30
 
22
- const reloadApp = async ({ server }: { server: Vite.ViteDevServer }): Promise<App | Error> => {
31
+ const isNeedAppLoadOrReload = (server: Vite.ViteDevServer): boolean => {
32
+ const appModule = server.moduleGraph.getModuleById(appModulePath)
33
+ if (!appModule) return true // Not loaded yet
34
+
35
+ // Check if the module or any of its dependencies are invalidated
36
+ const checkInvalidated = (mod: Vite.ModuleNode, visited = new Set<string>()): boolean => {
37
+ console.log(Obj.pick(mod, ['ssrInvalidationState', 'invalidationState', 'lastInvalidationTimestamp']))
38
+ // if (!mod.id || visited.has(mod.id)) return false
39
+ // visited.add(mod.id)
40
+
41
+ // // Check if this module is invalidated
42
+ // if (mod.transformResult === null) return true
43
+
44
+ // // Check all imported modules recursively
45
+ // for (const imported of mod.importedModules) {
46
+ // if (checkInvalidated(imported, visited)) return true
47
+ // }
48
+
49
+ return false
50
+ }
51
+
52
+ return checkInvalidated(appModule)
53
+ }
54
+
55
+ const reloadApp = async (server: Vite.ViteDevServer): Promise<App | Error> => {
23
56
  debug('reloadApp')
57
+
24
58
  return server.ssrLoadModule(config.paths.framework.template.server.app)
25
59
  .then(module => module as AppServerModule)
26
- .then(module => module.app)
60
+ .then(module => {
61
+ return module.createApp({
62
+ hooks: {
63
+ transformHtml: [
64
+ // Inject entry client script for development
65
+ (html: string, _ctx) => {
66
+ const entryClientPath = config.paths.framework.template.client.entrypoint
67
+ const entryClientScript = `<script type="module" src="${entryClientPath}"></script>`
68
+ return html.replace('</body>', `${entryClientScript}</body>`)
69
+ },
70
+ // Apply Vite's transformations
71
+ async (html: string, ctx) => {
72
+ return await server.transformIndexHtml(ctx.req.url, html)
73
+ },
74
+ ],
75
+ },
76
+ })
77
+ })
27
78
  .catch(async (error) => {
28
79
  if (Err.is(error)) {
29
80
  // ━ Clean Stack Trace
@@ -57,12 +108,12 @@ export const Serve = (
57
108
  handleHotUpdate({ server }) {
58
109
  debug('handleHotUpdate')
59
110
  // Reload app server immediately in the background
60
- appPromise = reloadApp({ server })
111
+ appPromise = reloadApp(server)
61
112
  },
62
113
  async configureServer(server) {
63
114
  debug('configureServer')
64
115
  // Initial load
65
- appPromise = reloadApp({ server })
116
+ appPromise = reloadApp(server)
66
117
 
67
118
  return () => {
68
119
  // Remove index.html serving middleware.
@@ -74,6 +125,8 @@ export const Serve = (
74
125
 
75
126
  // Add middleware that runs our entry server
76
127
  server.middlewares.use((req, res, ___next) => {
128
+ debug('request')
129
+ // isNeedAppLoadOrReload(server)
77
130
  void HonoNodeServer.getRequestListener(async request => {
78
131
  // Always await the current app promise
79
132
  const app = await appPromise
@@ -81,8 +134,8 @@ export const Serve = (
81
134
  // Err.log(app)
82
135
  return ResponseInternalServerError()
83
136
  }
84
- const response = await app.fetch(request, { viteDevServer: server })
85
- return response
137
+
138
+ return await app.fetch(request)
86
139
  })(req, res)
87
140
  })
88
141
  }
@@ -13,10 +13,10 @@ export const reportDiagnostics = (diagnostics: Diagnostic[]) => {
13
13
  infos.length > 0 && `${infos.length} info${infos.length === 1 ? '' : 's'}`,
14
14
  ].filter(Boolean).join(', ')
15
15
 
16
- console.warn(`\n🔍 Polen found ${summary}:\n`)
16
+ console.warn(`\nPolen found ${summary}:\n`)
17
17
 
18
18
  diagnostics.forEach((diagnostic, index) => {
19
- const icon = diagnostic.severity === 'error' ? '' : diagnostic.severity === 'warning' ? '⚠️ ' : 'ℹ️ '
19
+ const icon = diagnostic.severity === 'error' ? '' : diagnostic.severity === 'warning' ? '' : ''
20
20
  console.warn(`${icon} ${index + 1}. ${diagnostic.message}\n`)
21
21
  })
22
22
  }
@@ -59,7 +59,7 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
59
59
  onNavigate,
60
60
  validate = true,
61
61
  className = '',
62
- } = options || {}
62
+ } = options
63
63
 
64
64
  const navigate = useNavigate()
65
65
  const handleNavigate = onNavigate || ((url: string) => navigate(url))
@@ -93,8 +93,9 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
93
93
  const analysisResult = ReactHooks.useMemo(() => {
94
94
  if (plain) return null
95
95
  const result = analyze(children, { schema })
96
+ // Debug logging handled by debug prop
96
97
  return result
97
- }, [children, plain, schema])
98
+ }, [children, plain, schema, debug])
98
99
 
99
100
  // Layer 2: Schema resolution
100
101
  const resolver = ReactHooks.useMemo(() => {
@@ -130,6 +131,7 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
130
131
  // Prepare code block and calculate positions after render
131
132
  ReactHooks.useEffect(() => {
132
133
  if (!containerRef.current || !analysisResult || !positionCalculator || plain) {
134
+ // Skip position calculation - debug handled by debug prop
133
135
  return
134
136
  }
135
137
 
@@ -138,11 +140,13 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
138
140
  || containerRef.current.querySelector('pre code')
139
141
  || containerRef.current.querySelector('code')
140
142
  if (!codeElement) {
143
+ // No code element found - skip
141
144
  return
142
145
  }
143
146
 
144
147
  // Prepare the code block (wrap identifiers)
145
148
  const identifiers = Array.from(analysisResult.identifiers.byPosition.values())
149
+ // Prepare code block with identifiers
146
150
  positionCalculator.prepareCodeBlock(codeElement as Element, identifiers)
147
151
 
148
152
  // Get positions after DOM update
@@ -150,28 +154,36 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
150
154
  // Pass containerRef.current as the reference element for positioning
151
155
  if (containerRef.current) {
152
156
  const newPositions = positionCalculator.getIdentifierPositions(codeElement as Element, containerRef.current)
157
+ // Position calculation complete
153
158
  setPositions(newPositions)
154
159
  setIsReady(true)
155
160
  }
156
161
  })
157
162
  }, [analysisResult, positionCalculator, plain, highlightedHtml])
158
163
 
159
- // Handle resize events
164
+ // Handle resize events with debouncing
160
165
  ReactHooks.useEffect(() => {
161
166
  if (!containerRef.current || !positionCalculator || plain) return
162
167
 
168
+ let resizeTimer: NodeJS.Timeout
163
169
  const handleResize = () => {
164
- const codeElement = containerRef.current?.querySelector('pre.shiki code')
165
- || containerRef.current?.querySelector('pre code')
166
- || containerRef.current?.querySelector('code')
167
- if (codeElement && containerRef.current) {
168
- const newPositions = positionCalculator.getIdentifierPositions(codeElement as Element, containerRef.current)
169
- setPositions(newPositions)
170
- }
170
+ clearTimeout(resizeTimer)
171
+ resizeTimer = setTimeout(() => {
172
+ const codeElement = containerRef.current?.querySelector('pre.shiki code')
173
+ || containerRef.current?.querySelector('pre code')
174
+ || containerRef.current?.querySelector('code')
175
+ if (codeElement && containerRef.current) {
176
+ const newPositions = positionCalculator.getIdentifierPositions(codeElement as Element, containerRef.current)
177
+ setPositions(newPositions)
178
+ }
179
+ }, 100) // Debounce resize events
171
180
  }
172
181
 
173
182
  window.addEventListener('resize', handleResize)
174
- return () => window.removeEventListener('resize', handleResize)
183
+ return () => {
184
+ clearTimeout(resizeTimer)
185
+ window.removeEventListener('resize', handleResize)
186
+ }
175
187
  }, [positionCalculator, plain])
176
188
 
177
189
  // Validation errors
@@ -33,13 +33,11 @@ describe('Simple Positioning Engine', () => {
33
33
  const container = document.createElement('div')
34
34
  container.innerHTML = `
35
35
  <pre class="shiki">
36
- <code>
37
- <span class="line">query GetUser {</span>
38
- <span class="line"> user {</span>
39
- <span class="line"> name</span>
40
- <span class="line"> }</span>
41
- <span class="line">}</span>
42
- </code>
36
+ <code>query GetUser {
37
+ user {
38
+ name
39
+ }
40
+ }</code>
43
41
  </pre>
44
42
  `
45
43
 
@@ -49,7 +47,8 @@ describe('Simple Positioning Engine', () => {
49
47
  createTestIdentifier('name', 3, 5, 'Field'),
50
48
  ]
51
49
 
52
- calculator.prepareCodeBlock(container, identifiers)
50
+ const codeElement = container.querySelector('code')!
51
+ calculator.prepareCodeBlock(codeElement, identifiers)
53
52
 
54
53
  // Check that identifiers were wrapped
55
54
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -66,9 +65,7 @@ describe('Simple Positioning Engine', () => {
66
65
  const container = document.createElement('div')
67
66
  container.innerHTML = `
68
67
  <pre class="shiki">
69
- <code>
70
- <span class="line">query GetUserById($id: ID!) {</span>
71
- </code>
68
+ <code>query GetUserById($id: ID!) {</code>
72
69
  </pre>
73
70
  `
74
71
 
@@ -79,7 +76,8 @@ describe('Simple Positioning Engine', () => {
79
76
  createTestIdentifier('ID', 1, 24, 'Type'),
80
77
  ]
81
78
 
82
- calculator.prepareCodeBlock(container, identifiers)
79
+ const codeElement = container.querySelector('code')!
80
+ calculator.prepareCodeBlock(codeElement, identifiers)
83
81
 
84
82
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
85
83
  expect(wrappedElements.length).toBe(4)
@@ -140,9 +138,7 @@ describe('Simple Positioning Engine', () => {
140
138
  const container = document.createElement('div')
141
139
  container.innerHTML = `
142
140
  <pre class="shiki">
143
- <code>
144
- <span class="line"><span data-graphql-id="existing">user</span> {</span>
145
- </code>
141
+ <code><span data-graphql-id="existing">user</span> {</code>
146
142
  </pre>
147
143
  `
148
144
 
@@ -150,7 +146,8 @@ describe('Simple Positioning Engine', () => {
150
146
  createTestIdentifier('user', 1, 1),
151
147
  ]
152
148
 
153
- calculator.prepareCodeBlock(container, identifiers)
149
+ const codeElement = container.querySelector('code')!
150
+ calculator.prepareCodeBlock(codeElement, identifiers)
154
151
 
155
152
  // Should still only have one wrapped element
156
153
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -162,11 +159,9 @@ describe('Simple Positioning Engine', () => {
162
159
  const container = document.createElement('div')
163
160
  container.innerHTML = `
164
161
  <pre class="shiki">
165
- <code>
166
- <span class="line">query {</span>
167
- <span class="line"></span>
168
- <span class="line"> user</span>
169
- </code>
162
+ <code>query {
163
+
164
+ user</code>
170
165
  </pre>
171
166
  `
172
167
 
@@ -175,8 +170,9 @@ describe('Simple Positioning Engine', () => {
175
170
  createTestIdentifier('user', 3, 3),
176
171
  ]
177
172
 
173
+ const codeElement = container.querySelector('code')!
178
174
  expect(() => {
179
- calculator.prepareCodeBlock(container, identifiers)
175
+ calculator.prepareCodeBlock(codeElement, identifiers)
180
176
  }).not.toThrow()
181
177
 
182
178
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -50,21 +50,104 @@ export class SimplePositionCalculator {
50
50
  containerElement: Element,
51
51
  identifiers: Identifier[],
52
52
  ): void {
53
- // Sort all identifiers by position (right to left, bottom to top)
54
- const sortedIdentifiers = [...identifiers].sort((a, b) => {
55
- // Sort by line first (bottom to top)
56
- if (a.position.line !== b.position.line) {
57
- return b.position.line - a.position.line
58
- }
59
- // Then by column (right to left)
60
- return b.position.column - a.position.column
61
- })
53
+ // Get the full text content of the container
54
+ const fullText = containerElement.textContent || ''
55
+ const lines = fullText.split('\n')
56
+
57
+ // Build a map of line start positions in the full text
58
+ const lineStartPositions: number[] = [0]
59
+ for (let i = 0; i < lines.length - 1; i++) {
60
+ const lineLength = lines[i]?.length ?? 0
61
+ lineStartPositions.push(lineStartPositions[i]! + lineLength + 1) // +1 for newline
62
+ }
63
+
64
+ // Process identifiers by line
65
+ for (const identifier of identifiers) {
66
+ const lineIndex = identifier.position.line - 1
67
+ if (lineIndex >= lines.length || lineIndex < 0) continue
68
+
69
+ const lineText = lines[lineIndex]
70
+ if (!lineText) continue
71
+
72
+ const columnIndex = identifier.position.column - 1
73
+
74
+ // Check if the identifier exists at the expected position
75
+ if (lineText.substring(columnIndex).startsWith(identifier.name)) {
76
+ // Calculate the absolute position in the full text
77
+ const lineStartPosition = lineStartPositions[lineIndex] ?? 0
78
+ const absolutePosition = lineStartPosition + columnIndex
79
+
80
+ // Check if already wrapped at this specific position
81
+ const existingWrapped = containerElement.querySelectorAll(`[data-graphql-id]`)
82
+ let alreadyWrapped = false
83
+ for (const wrapped of existingWrapped) {
84
+ if (wrapped.textContent === identifier.name) {
85
+ const startPos = parseInt(wrapped.getAttribute('data-graphql-start') || '0')
86
+ if (startPos === identifier.position.start) {
87
+ alreadyWrapped = true
88
+ break
89
+ }
90
+ }
91
+ }
92
+ if (alreadyWrapped) continue
93
+
94
+ // Create wrapper span
95
+ const wrapper = document.createElement('span')
96
+ const id = `${identifier.position.start}-${identifier.name}-${identifier.kind}`
97
+ wrapper.setAttribute('data-graphql-id', id)
98
+ wrapper.setAttribute('data-graphql-name', identifier.name)
99
+ wrapper.setAttribute('data-graphql-kind', identifier.kind)
100
+ wrapper.setAttribute('data-graphql-start', String(identifier.position.start))
101
+ wrapper.setAttribute('data-graphql-end', String(identifier.position.end))
102
+ wrapper.setAttribute('data-graphql-line', String(identifier.position.line))
103
+ wrapper.setAttribute('data-graphql-column', String(identifier.position.column))
104
+ wrapper.setAttribute('data-graphql-path', identifier.schemaPath.join(','))
105
+
106
+ // Find the position in the container and wrap the text
107
+ const walker = document.createTreeWalker(
108
+ containerElement,
109
+ NodeFilter.SHOW_TEXT,
110
+ null,
111
+ )
112
+
113
+ let currentPos = 0
114
+ let node: Node | null
115
+
116
+ while (node = walker.nextNode()) {
117
+ const textNode = node as Text
118
+ const text = textNode.textContent || ''
119
+
120
+ // Check if this text node contains our identifier
121
+ if (currentPos <= absolutePosition && absolutePosition < currentPos + text.length) {
122
+ const relativePos = absolutePosition - currentPos
123
+
124
+ if (text.substring(relativePos).startsWith(identifier.name)) {
125
+ // Split the text node
126
+ const before = text.substring(0, relativePos)
127
+ const identifierText = identifier.name
128
+ const after = text.substring(relativePos + identifierText.length)
129
+
130
+ const parent = textNode.parentNode!
131
+
132
+ if (before) {
133
+ parent.insertBefore(document.createTextNode(before), textNode)
134
+ }
135
+
136
+ wrapper.textContent = identifierText
137
+ parent.insertBefore(wrapper, textNode)
138
+
139
+ if (after) {
140
+ parent.insertBefore(document.createTextNode(after), textNode)
141
+ }
142
+
143
+ parent.removeChild(textNode)
144
+ break
145
+ }
146
+ }
62
147
 
63
- // Process all identifiers
64
- let wrappedCount = 0
65
- for (const identifier of sortedIdentifiers) {
66
- const wrapped = this.wrapIdentifier(containerElement, identifier)
67
- if (wrapped) wrappedCount++
148
+ currentPos += text.length
149
+ }
150
+ }
68
151
  }
69
152
  }
70
153
 
@@ -117,100 +200,6 @@ export class SimplePositionCalculator {
117
200
 
118
201
  return results
119
202
  }
120
-
121
- /**
122
- * Wrap an identifier in a span for positioning
123
- * Returns true if the identifier was successfully wrapped
124
- */
125
- private wrapIdentifier(containerElement: Element, identifier: Identifier): boolean {
126
- const walker = document.createTreeWalker(
127
- containerElement,
128
- NodeFilter.SHOW_TEXT,
129
- null,
130
- )
131
-
132
- let currentLine = 1
133
- let currentColumn = 1
134
- let node: Node | null
135
-
136
- while (node = walker.nextNode()) {
137
- const textNode = node as Text
138
- const text = textNode.textContent || ''
139
-
140
- // Check if already wrapped
141
- if (textNode.parentElement?.hasAttribute('data-graphql-id')) {
142
- // Update position tracking and continue
143
- for (const char of text) {
144
- if (char === '\n') {
145
- currentLine++
146
- currentColumn = 1
147
- } else {
148
- currentColumn++
149
- }
150
- }
151
- continue
152
- }
153
-
154
- // Track position in the text
155
- let textIndex = 0
156
- while (textIndex < text.length) {
157
- // Check if we're at the identifier's position
158
- if (
159
- currentLine === identifier.position.line
160
- && currentColumn === identifier.position.column
161
- ) {
162
- // Verify it's actually our identifier
163
- const remainingText = text.substring(textIndex)
164
- if (remainingText.startsWith(identifier.name)) {
165
- // Create a unique ID for this identifier
166
- const id = `${identifier.position.start}-${identifier.name}-${identifier.kind}`
167
-
168
- // Split the text node and wrap the identifier
169
- const before = text.substring(0, textIndex)
170
- const after = text.substring(textIndex + identifier.name.length)
171
-
172
- const span = document.createElement('span')
173
- span.setAttribute('data-graphql-id', id)
174
- span.setAttribute('data-graphql-name', identifier.name)
175
- span.setAttribute('data-graphql-kind', identifier.kind)
176
- span.setAttribute('data-graphql-start', String(identifier.position.start))
177
- span.setAttribute('data-graphql-end', String(identifier.position.end))
178
- span.setAttribute('data-graphql-line', String(identifier.position.line))
179
- span.setAttribute('data-graphql-column', String(identifier.position.column))
180
- span.setAttribute('data-graphql-path', identifier.schemaPath.join(','))
181
- span.textContent = identifier.name
182
-
183
- const parent = textNode.parentNode!
184
-
185
- if (before) {
186
- parent.insertBefore(document.createTextNode(before), textNode)
187
- }
188
-
189
- parent.insertBefore(span, textNode)
190
-
191
- if (after) {
192
- parent.insertBefore(document.createTextNode(after), textNode)
193
- }
194
-
195
- parent.removeChild(textNode)
196
- return true
197
- }
198
- }
199
-
200
- // Update position tracking
201
- const char = text[textIndex]
202
- if (char === '\n') {
203
- currentLine++
204
- currentColumn = 1
205
- } else {
206
- currentColumn++
207
- }
208
- textIndex++
209
- }
210
- }
211
-
212
- return false // Identifier not found
213
- }
214
203
  }
215
204
 
216
205
  /**
@@ -74,8 +74,13 @@ describe('property-based tests', () => {
74
74
  expect(inAllowed).toBe(!inDenied)
75
75
  })
76
76
 
77
- // Combined they reconstruct the original object
78
- expect({ ...allowed, ...denied }).toEqual(obj)
77
+ // Combined they reconstruct the original object (only own properties)
78
+ const reconstructed = { ...allowed, ...denied }
79
+ const ownPropsObj = Object.keys(obj).reduce((acc, key) => {
80
+ acc[key] = obj[key]
81
+ return acc
82
+ }, {} as any)
83
+ expect(reconstructed).toEqual(ownPropsObj)
79
84
  },
80
85
  ),
81
86
  )
@@ -87,7 +92,14 @@ describe('property-based tests', () => {
87
92
  fc.object(),
88
93
  (obj) => {
89
94
  const filtered = objFilter(obj, () => true)
90
- expect(filtered).toEqual(obj)
95
+
96
+ // Object.keys doesn't include __proto__, so we need to handle it specially
97
+ const objWithoutProto = Object.keys(obj).reduce((acc, key) => {
98
+ acc[key] = obj[key]
99
+ return acc
100
+ }, {} as any)
101
+
102
+ expect(filtered).toEqual(objWithoutProto)
91
103
 
92
104
  // Values are the same reference
93
105
  Object.keys(filtered).forEach(key => {