@emeryld/rrroutes-contract 2.7.4 → 2.7.6

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.
@@ -140,6 +140,12 @@
140
140
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
141
141
  }
142
142
 
143
+ .grid-3 {
144
+ display: grid;
145
+ gap: 6px;
146
+ grid-template-columns: repeat(3, minmax(0, 1fr));
147
+ }
148
+
143
149
  .kv {
144
150
  border-bottom: 1px dashed #e2e8f2;
145
151
  padding: 4px 0 6px;
@@ -179,6 +185,30 @@
179
185
  background: #effbf4;
180
186
  }
181
187
 
188
+ .icon-row {
189
+ display: flex;
190
+ gap: 6px;
191
+ margin-top: 2px;
192
+ }
193
+
194
+ .icon-badge {
195
+ display: inline-flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ width: 18px;
199
+ height: 18px;
200
+ border: 1px solid #e0e6f0;
201
+ border-radius: 4px;
202
+ font-size: 11px;
203
+ background: #f6f8fc;
204
+ }
205
+
206
+ .icon-badge.warn {
207
+ border-color: #f2d5d5;
208
+ color: #a02424;
209
+ background: #fff4f4;
210
+ }
211
+
182
212
  .empty {
183
213
  color: var(--muted);
184
214
  }
@@ -244,6 +274,12 @@
244
274
  font-size: 11px;
245
275
  background: #f2f5fa;
246
276
  }
277
+
278
+ @media (max-width: 720px) {
279
+ .grid-3 {
280
+ grid-template-columns: 1fr;
281
+ }
282
+ }
247
283
  </style>
248
284
  </head>
249
285
  <body>
@@ -260,11 +296,6 @@
260
296
  <input id="searchInput" type="text" placeholder="Type to search..." />
261
297
  </label>
262
298
 
263
- <label>
264
- Type filter:
265
- <input id="typeFilterInput" type="text" placeholder='Type/kind/enum (e.g. "paid")' />
266
- </label>
267
-
268
299
  <div class="field-row">
269
300
  <label class="field-item">
270
301
  <input id="caseSensitive" type="checkbox" />
@@ -274,10 +305,6 @@
274
305
  <input id="regexSearch" type="checkbox" />
275
306
  <span>regex</span>
276
307
  </label>
277
- <label class="field-item">
278
- <input id="hasEnumOnly" type="checkbox" />
279
- <span>has enum only</span>
280
- </label>
281
308
  <label class="field-item">
282
309
  <span>type match</span>
283
310
  <select id="typeMatchMode">
@@ -285,6 +312,10 @@
285
312
  <option value="exact">exact</option>
286
313
  </select>
287
314
  </label>
315
+ <label class="field-item">
316
+ <input id="schemaRowsMatchOnly" type="checkbox" />
317
+ <span>schema rows match filter</span>
318
+ </label>
288
319
  </div>
289
320
 
290
321
  <div id="fieldCheckboxes" class="field-row"></div>
@@ -319,6 +350,36 @@
319
350
  { id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
320
351
  { id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
321
352
  { id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
353
+ {
354
+ id: 'sourceDefinition',
355
+ label: 'source definition',
356
+ get: (leaf, schemaFlatByLeaf, payload) => {
357
+ const source = payload?.sourceByLeaf?.[leaf.key]?.definition
358
+ if (!source) return []
359
+ return [`${source.file}:${source.line}:${source.column}`]
360
+ },
361
+ },
362
+ {
363
+ id: 'sourceSchemas',
364
+ label: 'source schemas',
365
+ get: (leaf, schemaFlatByLeaf, payload) => {
366
+ const schemas = payload?.sourceByLeaf?.[leaf.key]?.schemas
367
+ if (!schemas || typeof schemas !== 'object') return []
368
+ return Object.values(schemas).flatMap((schema) => {
369
+ if (!schema || typeof schema !== 'object') return []
370
+ const tokens = []
371
+ if (schema.sourceName) tokens.push(schema.sourceName)
372
+ if (schema.tag) tokens.push(schema.tag)
373
+ tokens.push(`${schema.file}:${schema.line}:${schema.column}`)
374
+ return tokens
375
+ })
376
+ },
377
+ },
378
+ {
379
+ id: 'types',
380
+ label: 'types',
381
+ get: (leaf, schemaFlatByLeaf) => schemaTypeTokens(schemaFlatByLeaf?.[leaf.key]),
382
+ },
322
383
  {
323
384
  id: 'params',
324
385
  label: 'params',
@@ -355,11 +416,10 @@
355
416
 
356
417
  const fileInput = document.getElementById('fileInput')
357
418
  const searchInput = document.getElementById('searchInput')
358
- const typeFilterInput = document.getElementById('typeFilterInput')
359
419
  const caseSensitiveInput = document.getElementById('caseSensitive')
360
420
  const regexSearchInput = document.getElementById('regexSearch')
361
- const hasEnumOnlyInput = document.getElementById('hasEnumOnly')
362
421
  const typeMatchModeInput = document.getElementById('typeMatchMode')
422
+ const schemaRowsMatchOnlyInput = document.getElementById('schemaRowsMatchOnly')
363
423
  const fieldCheckboxes = document.getElementById('fieldCheckboxes')
364
424
  const activeFilterChips = document.getElementById('activeFilterChips')
365
425
  const selectAllFieldsBtn = document.getElementById('selectAllFields')
@@ -394,6 +454,9 @@
394
454
  if (!query) {
395
455
  return {
396
456
  active: false,
457
+ query,
458
+ caseSensitive,
459
+ regex,
397
460
  error: null,
398
461
  test: () => true,
399
462
  highlight: (text) => escapeHtml(text ?? ''),
@@ -406,6 +469,9 @@
406
469
  const rx = new RegExp(query, flags)
407
470
  return {
408
471
  active: true,
472
+ query,
473
+ caseSensitive,
474
+ regex,
409
475
  error: null,
410
476
  test: (text) => {
411
477
  const source = String(text ?? '')
@@ -423,6 +489,9 @@
423
489
  } catch (error) {
424
490
  return {
425
491
  active: true,
492
+ query,
493
+ caseSensitive,
494
+ regex,
426
495
  error: error instanceof Error ? error.message : 'Invalid regex',
427
496
  test: () => false,
428
497
  highlight: (text) => escapeHtml(text ?? ''),
@@ -436,6 +505,9 @@
436
505
 
437
506
  return {
438
507
  active: true,
508
+ query,
509
+ caseSensitive,
510
+ regex,
439
511
  error: null,
440
512
  test: (text) => {
441
513
  const source = String(text ?? '')
@@ -499,23 +571,77 @@
499
571
  return flatSchemaToSearchTokens(Object.fromEntries(sectionEntries))
500
572
  }
501
573
 
502
- function typeFilterTokensByLeaf(flatSchema) {
503
- if (!flatSchema || typeof flatSchema !== 'object') return { tokens: [], hasEnum: false }
574
+ function schemaTypeTokens(flatSchema) {
575
+ if (!flatSchema || typeof flatSchema !== 'object') return []
504
576
 
505
577
  const tokens = new Set()
506
- let hasEnum = false
507
-
508
578
  Object.values(flatSchema).forEach((info) => {
509
579
  if (!info || typeof info !== 'object') return
510
580
  if (info.type) tokens.add(String(info.type))
511
581
  if (info.kind) tokens.add(String(info.kind))
512
- if (Array.isArray(info.enumValues) && info.enumValues.length > 0) {
513
- hasEnum = true
582
+ if (Array.isArray(info.enumValues)) {
514
583
  info.enumValues.forEach((value) => tokens.add(String(value)))
515
584
  }
516
585
  })
517
586
 
518
- return { tokens: Array.from(tokens), hasEnum }
587
+ return Array.from(tokens)
588
+ }
589
+
590
+ function schemaEntryTokens(path, info) {
591
+ const tokens = new Set()
592
+
593
+ if (path) {
594
+ tokens.add(path)
595
+ path.split('.').forEach((segment) => {
596
+ if (segment) tokens.add(segment)
597
+ })
598
+ }
599
+
600
+ if (info && typeof info === 'object') {
601
+ tokens.add(JSON.stringify(info))
602
+ if (info.type) tokens.add(String(info.type))
603
+ if (info.kind) tokens.add(String(info.kind))
604
+ if (info.description) tokens.add(String(info.description))
605
+ if (Array.isArray(info.enumValues)) {
606
+ info.enumValues.forEach((value) => tokens.add(String(value)))
607
+ }
608
+ } else if (info !== null && info !== undefined) {
609
+ tokens.add(String(info))
610
+ }
611
+
612
+ return Array.from(tokens)
613
+ }
614
+
615
+ function schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds) {
616
+ if (!engine.active) return true
617
+
618
+ let matched = false
619
+ if (selectedIds.includes(sectionName)) {
620
+ matched = schemaEntryTokens(fullPath, info).some((token) => engine.test(token))
621
+ }
622
+
623
+ if (!matched && selectedIds.includes('types')) {
624
+ const typeTokens = []
625
+ if (info && typeof info === 'object') {
626
+ if (info.type) typeTokens.push(String(info.type))
627
+ if (info.kind) typeTokens.push(String(info.kind))
628
+ if (Array.isArray(info.enumValues)) {
629
+ info.enumValues.forEach((value) => typeTokens.push(String(value)))
630
+ }
631
+ }
632
+
633
+ if (typeMatchModeInput.value === 'exact' && !engine.regex) {
634
+ const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
635
+ const normalized = engine.caseSensitive
636
+ ? typeTokens
637
+ : typeTokens.map((token) => token.toLowerCase())
638
+ matched = normalized.includes(needle)
639
+ } else {
640
+ matched = typeTokens.some((token) => engine.test(token))
641
+ }
642
+ }
643
+
644
+ return matched
519
645
  }
520
646
 
521
647
  function selectedFieldIds() {
@@ -529,50 +655,22 @@
529
655
  if (!engine.active) return true
530
656
  if (selectedIds.length === 0) return false
531
657
  const schemaFlatByLeaf = state.payload?.schemaFlatByLeaf || {}
658
+ const payload = state.payload || {}
532
659
 
533
660
  return SEARCH_FIELDS.some((field) => {
534
661
  if (!selectedIds.includes(field.id)) return false
535
- const tokens = toTokens(field.get(leaf, schemaFlatByLeaf))
662
+ const tokens = toTokens(field.get(leaf, schemaFlatByLeaf, payload))
663
+ if (field.id === 'types' && typeMatchModeInput.value === 'exact' && !engine.regex) {
664
+ const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
665
+ const normalized = engine.caseSensitive
666
+ ? tokens
667
+ : tokens.map((token) => String(token).toLowerCase())
668
+ return normalized.includes(needle)
669
+ }
536
670
  return tokens.some((token) => engine.test(token))
537
671
  })
538
672
  }
539
673
 
540
- function matchesTypeFilter(leaf, typeEngine, matchMode, hasEnumOnly) {
541
- const schemaFlatByLeaf = state.payload?.schemaFlatByLeaf || {}
542
- const { tokens, hasEnum } = typeFilterTokensByLeaf(schemaFlatByLeaf?.[leaf.key])
543
-
544
- if (hasEnumOnly && !hasEnum) return false
545
- if (!typeEngine.active) return true
546
-
547
- if (matchMode === 'exact') {
548
- const sourceTokens = typeEngine.caseSensitive
549
- ? tokens
550
- : tokens.map((token) => token.toLowerCase())
551
- const needle = typeEngine.caseSensitive ? typeEngine.query : typeEngine.query.toLowerCase()
552
- return sourceTokens.includes(needle)
553
- }
554
-
555
- return tokens.some((token) => typeEngine.test(token))
556
- }
557
-
558
- function createTypeSearchEngine(queryRaw) {
559
- const query = queryRaw || ''
560
- const caseSensitive = Boolean(caseSensitiveInput.checked)
561
- if (!query) return { active: false, query, caseSensitive, test: () => true }
562
-
563
- const needle = caseSensitive ? query : query.toLowerCase()
564
- return {
565
- active: true,
566
- query,
567
- caseSensitive,
568
- test: (text) => {
569
- const source = String(text ?? '')
570
- const hay = caseSensitive ? source : source.toLowerCase()
571
- return hay.includes(needle)
572
- },
573
- }
574
- }
575
-
576
674
  function el(tag, className, text) {
577
675
  const node = document.createElement(tag)
578
676
  if (className) node.className = className
@@ -619,6 +717,35 @@
619
717
  return row
620
718
  }
621
719
 
720
+ function sourceHref(source) {
721
+ if (!source || !source.file) return undefined
722
+ const normalizedPath = String(source.file).replace(/\\/g, '/')
723
+ const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'
724
+ return `${prefix}${encodeURI(normalizedPath)}`
725
+ }
726
+
727
+ function createSourceRow(key, source, engine) {
728
+ const box = el('div', 'kv')
729
+ box.appendChild(el('div', 'k', key))
730
+ const valueNode = el('div', 'v mono')
731
+
732
+ if (!source || !source.file) {
733
+ setHighlighted(valueNode, '—', engine)
734
+ box.appendChild(valueNode)
735
+ return box
736
+ }
737
+
738
+ const href = sourceHref(source)
739
+ const link = document.createElement('a')
740
+ link.href = href
741
+ link.target = '_blank'
742
+ link.rel = 'noopener noreferrer'
743
+ link.textContent = `${source.file}:${source.line}:${source.column}`
744
+ valueNode.appendChild(link)
745
+ box.appendChild(valueNode)
746
+ return box
747
+ }
748
+
622
749
  function splitFlatSchemaBySection(flatSchema) {
623
750
  const result = {
624
751
  params: {},
@@ -727,33 +854,44 @@
727
854
  return row
728
855
  }
729
856
 
730
- function renderSeparatedSchemas(flatSchema, engine) {
857
+ function renderSeparatedSchemas(flatSchema, engine, selectedIds) {
858
+ if (!flatSchema || typeof flatSchema !== 'object') return null
731
859
  const section = el('div', 'section')
732
860
  section.appendChild(el('h3', '', 'Schemas (separated by section)'))
733
861
 
734
862
  const grouped = splitFlatSchemaBySection(flatSchema)
863
+ let hasAnySchemaEntries = false
864
+ const limitToMatchedRows = schemaRowsMatchOnlyInput.checked && engine.active
735
865
 
736
866
  SCHEMA_SECTIONS.forEach((sectionName) => {
867
+ const rawEntries = grouped[sectionName]
868
+ if (!rawEntries || Object.keys(rawEntries).length === 0) return
869
+
870
+ const entries = limitToMatchedRows
871
+ ? Object.fromEntries(
872
+ Object.entries(rawEntries).filter(([fullPath, info]) =>
873
+ schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds),
874
+ ),
875
+ )
876
+ : rawEntries
877
+ if (Object.keys(entries).length === 0) return
878
+
879
+ hasAnySchemaEntries = true
737
880
  const block = el('div', 'schema-block')
738
881
  const header = el('div', 'schema-header mono')
739
882
  setHighlighted(header, sectionName, engine)
740
883
  block.appendChild(header)
741
884
 
742
- const entries = grouped[sectionName]
743
- if (!entries || Object.keys(entries).length === 0) {
744
- block.appendChild(el('div', 'empty', 'No entries'))
745
- } else {
746
- const tree = buildSchemaTree(entries, sectionName)
747
- block.appendChild(renderTreeNode(tree, engine, true))
748
- }
885
+ const tree = buildSchemaTree(entries, sectionName)
886
+ block.appendChild(renderTreeNode(tree, engine, true))
749
887
 
750
888
  section.appendChild(block)
751
889
  })
752
890
 
753
- return section
891
+ return hasAnySchemaEntries ? section : null
754
892
  }
755
893
 
756
- function renderLeaf(leaf, engine) {
894
+ function renderLeaf(leaf, engine, selectedIds) {
757
895
  const details = el('details', 'leaf')
758
896
  const summary = el('summary')
759
897
  setHighlighted(summary, `${String(leaf.method || '').toUpperCase()} ${leaf.path || ''}`, engine)
@@ -765,60 +903,43 @@
765
903
 
766
904
  const overview = el('div', 'section')
767
905
  overview.appendChild(el('h3', '', 'Overview'))
768
- const grid = el('div', 'grid-2')
769
- grid.appendChild(kv('key', leaf.key, engine))
770
- grid.appendChild(kv('method', leaf.method, engine))
771
- grid.appendChild(kv('path', leaf.path, engine))
772
- grid.appendChild(kv('group', cfg.docsGroup, engine))
773
- grid.appendChild(kv('stability', cfg.stability, engine))
774
- grid.appendChild(kv('feed', cfg.feed ? 'true' : 'false', engine))
775
- grid.appendChild(kv('deprecated', cfg.deprecated ? 'true' : 'false', engine))
776
- grid.appendChild(kv('hidden', cfg.docsHidden ? 'true' : 'false', engine))
777
- overview.appendChild(grid)
778
- content.appendChild(overview)
779
-
780
- const docs = el('div', 'section')
781
- docs.appendChild(el('h3', '', 'Documentation'))
782
- const docGrid = el('div', 'grid-2')
783
- docGrid.appendChild(kv('summary', cfg.summary, engine))
784
- docGrid.appendChild(kv('description', cfg.description, engine))
785
- docs.appendChild(docGrid)
786
-
787
- const tagsRow = el('div', 'chips')
788
- ;(cfg.tags || []).forEach((tag) => {
789
- const chip = el('span', 'chip')
790
- setHighlighted(chip, tag, engine)
791
- tagsRow.appendChild(chip)
792
- })
793
- if ((cfg.tags || []).length === 0) {
794
- tagsRow.appendChild(el('span', 'empty', 'No tags'))
906
+ const topGrid = el('div', 'grid-3')
907
+ topGrid.appendChild(kv('group', cfg.docsGroup, engine))
908
+ topGrid.appendChild(
909
+ kv('tags', cfg.tags && cfg.tags.length > 0 ? cfg.tags.join(', ') : undefined, engine),
910
+ )
911
+ topGrid.appendChild(kv('stability', cfg.stability, engine))
912
+ overview.appendChild(topGrid)
913
+ overview.appendChild(kv('summary', cfg.summary, engine))
914
+ overview.appendChild(kv('description', cfg.description, engine))
915
+
916
+ const iconRow = el('div', 'icon-row')
917
+ if (cfg.feed) {
918
+ const feed = el('span', 'icon-badge')
919
+ feed.title = 'Feed'
920
+ setHighlighted(feed, 'F', engine)
921
+ iconRow.appendChild(feed)
795
922
  }
796
- docs.appendChild(tagsRow)
797
-
798
- if (cfg.docsMeta && Object.keys(cfg.docsMeta).length > 0) {
799
- Object.entries(cfg.docsMeta).forEach(([k, v]) => {
800
- docs.appendChild(kv(`meta.${k}`, typeof v === 'string' ? v : JSON.stringify(v), engine))
801
- })
923
+ if (cfg.deprecated) {
924
+ const deprecated = el('span', 'icon-badge warn')
925
+ deprecated.title = 'Deprecated'
926
+ setHighlighted(deprecated, 'D', engine)
927
+ iconRow.appendChild(deprecated)
802
928
  }
803
-
804
- content.appendChild(docs)
805
-
806
- const schemas = el('div', 'section')
807
- schemas.appendChild(el('h3', '', 'Schema Summaries'))
808
- const schemaGrid = el('div', 'grid-2')
809
- const schemaObj = cfg.schemas || {}
810
- schemaGrid.appendChild(renderSchemaSummary('params', schemaObj.params, engine))
811
- schemaGrid.appendChild(renderSchemaSummary('query', schemaObj.query, engine))
812
- schemaGrid.appendChild(renderSchemaSummary('body', schemaObj.body, engine))
813
- schemaGrid.appendChild(renderSchemaSummary('output', schemaObj.output, engine))
814
- schemaGrid.appendChild(renderSchemaSummary('outputMeta', schemaObj.outputMeta, engine))
815
- schemaGrid.appendChild(renderSchemaSummary('queryExtension', schemaObj.queryExtension, engine))
816
- schemas.appendChild(schemaGrid)
817
- content.appendChild(schemas)
929
+ if (cfg.docsHidden) {
930
+ const hidden = el('span', 'icon-badge warn')
931
+ hidden.title = 'Hidden'
932
+ setHighlighted(hidden, 'H', engine)
933
+ iconRow.appendChild(hidden)
934
+ }
935
+ if (iconRow.childNodes.length > 0) {
936
+ overview.appendChild(iconRow)
937
+ }
938
+ content.appendChild(overview)
818
939
 
819
940
  const files = el('div', 'section')
820
- files.appendChild(el('h3', '', 'Body Files'))
821
941
  if (Array.isArray(cfg.bodyFiles) && cfg.bodyFiles.length > 0) {
942
+ files.appendChild(el('h3', '', 'Body Files'))
822
943
  const chips = el('div', 'chips')
823
944
  cfg.bodyFiles.forEach((file) => {
824
945
  const chip = el('span', 'chip ok')
@@ -826,12 +947,38 @@
826
947
  chips.appendChild(chip)
827
948
  })
828
949
  files.appendChild(chips)
829
- } else {
830
- files.appendChild(el('div', 'empty', 'No file upload fields.'))
950
+ content.appendChild(files)
951
+ }
952
+
953
+ const sourceByLeaf = state.payload?.sourceByLeaf || {}
954
+ const source = sourceByLeaf[leaf.key]
955
+ if (source) {
956
+ const sourceSection = el('div', 'section')
957
+ sourceSection.appendChild(el('h3', '', 'Source'))
958
+ sourceSection.appendChild(createSourceRow('definition', source.definition, engine))
959
+
960
+ const schemaSources = source.schemas || {}
961
+ const schemaEntries = Object.entries(schemaSources)
962
+ if (schemaEntries.length > 0) {
963
+ const schemaGrid = el('div', 'grid-2')
964
+ schemaEntries.forEach(([schemaName, schemaSource]) => {
965
+ const display = schemaSource?.sourceName
966
+ ? `${schemaName}: ${schemaSource.sourceName}`
967
+ : schemaSource?.tag
968
+ ? `${schemaName}: ${schemaSource.tag}`
969
+ : schemaName
970
+ schemaGrid.appendChild(createSourceRow(display, schemaSource, engine))
971
+ })
972
+ sourceSection.appendChild(schemaGrid)
973
+ }
974
+
975
+ content.appendChild(sourceSection)
831
976
  }
832
- content.appendChild(files)
833
977
 
834
- content.appendChild(renderSeparatedSchemas(flatSchema, engine))
978
+ const separatedSchemas = renderSeparatedSchemas(flatSchema, engine, selectedIds)
979
+ if (separatedSchemas) {
980
+ content.appendChild(separatedSchemas)
981
+ }
835
982
 
836
983
  details.appendChild(content)
837
984
  return details
@@ -842,9 +989,6 @@
842
989
  caseSensitive: caseSensitiveInput.checked,
843
990
  regex: regexSearchInput.checked,
844
991
  })
845
- const typeEngine = createTypeSearchEngine(typeFilterInput.value.trim())
846
- const hasEnumOnly = Boolean(hasEnumOnlyInput.checked)
847
- const typeMatchMode = typeMatchModeInput.value === 'exact' ? 'exact' : 'contains'
848
992
 
849
993
  if (engine.error) {
850
994
  statusEl.textContent = `Invalid regex: ${engine.error}`
@@ -858,11 +1002,7 @@
858
1002
  }
859
1003
 
860
1004
  const selectedIds = selectedFieldIds()
861
- const filtered = state.leaves.filter(
862
- (leaf) =>
863
- matchesLeaf(leaf, engine, selectedIds) &&
864
- matchesTypeFilter(leaf, typeEngine, typeMatchMode, hasEnumOnly),
865
- )
1005
+ const filtered = state.leaves.filter((leaf) => matchesLeaf(leaf, engine, selectedIds))
866
1006
 
867
1007
  statusEl.textContent = `${filtered.length} / ${state.leaves.length} routes matched.`
868
1008
  resultsEl.innerHTML = ''
@@ -875,7 +1015,7 @@
875
1015
  }
876
1016
 
877
1017
  filtered.forEach((leaf) => {
878
- resultsEl.appendChild(renderLeaf(leaf, engine))
1018
+ resultsEl.appendChild(renderLeaf(leaf, engine, selectedIds))
879
1019
  })
880
1020
  syncFilterStateToUrl()
881
1021
  }
@@ -906,11 +1046,10 @@
906
1046
 
907
1047
  function resetFiltersToDefault() {
908
1048
  searchInput.value = ''
909
- typeFilterInput.value = ''
910
1049
  caseSensitiveInput.checked = false
911
1050
  regexSearchInput.checked = false
912
- hasEnumOnlyInput.checked = false
913
1051
  typeMatchModeInput.value = 'contains'
1052
+ schemaRowsMatchOnlyInput.checked = false
914
1053
  setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id)))
915
1054
  }
916
1055
 
@@ -919,14 +1058,12 @@
919
1058
  const chips = []
920
1059
 
921
1060
  const searchValue = searchInput.value.trim()
922
- const typeValue = typeFilterInput.value.trim()
923
1061
 
924
1062
  if (searchValue) chips.push(`search: ${searchValue}`)
925
- if (typeValue) chips.push(`type: ${typeValue}`)
926
1063
  if (typeMatchModeInput.value === 'exact') chips.push('type mode: exact')
1064
+ if (schemaRowsMatchOnlyInput.checked) chips.push('schema rows: matched only')
927
1065
  if (caseSensitiveInput.checked) chips.push('case sensitive')
928
1066
  if (regexSearchInput.checked) chips.push('regex')
929
- if (hasEnumOnlyInput.checked) chips.push('has enum only')
930
1067
  if (hasRegexError) chips.push('regex error')
931
1068
 
932
1069
  const allIds = SEARCH_FIELDS.map((field) => field.id)
@@ -949,11 +1086,10 @@
949
1086
  function collectFilterState() {
950
1087
  return {
951
1088
  search: searchInput.value,
952
- typeFilter: typeFilterInput.value,
953
1089
  caseSensitive: caseSensitiveInput.checked,
954
1090
  regex: regexSearchInput.checked,
955
- hasEnumOnly: hasEnumOnlyInput.checked,
956
1091
  typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
1092
+ schemaRowsMatchOnly: schemaRowsMatchOnlyInput.checked,
957
1093
  selectedFields: selectedFieldIds(),
958
1094
  }
959
1095
  }
@@ -980,13 +1116,12 @@
980
1116
  const parsed = JSON.parse(decodeURIComponent(raw))
981
1117
  isHydratingFromUrl = true
982
1118
  if (typeof parsed.search === 'string') searchInput.value = parsed.search
983
- if (typeof parsed.typeFilter === 'string') typeFilterInput.value = parsed.typeFilter
984
1119
  caseSensitiveInput.checked = Boolean(parsed.caseSensitive)
985
1120
  regexSearchInput.checked = Boolean(parsed.regex)
986
- hasEnumOnlyInput.checked = Boolean(parsed.hasEnumOnly)
987
1121
  if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
988
1122
  typeMatchModeInput.value = parsed.typeMatchMode
989
1123
  }
1124
+ schemaRowsMatchOnlyInput.checked = Boolean(parsed.schemaRowsMatchOnly)
990
1125
 
991
1126
  if (Array.isArray(parsed.selectedFields)) {
992
1127
  const allowed = new Set(parsed.selectedFields)
@@ -1038,11 +1173,10 @@
1038
1173
  })
1039
1174
 
1040
1175
  searchInput.addEventListener('input', renderResults)
1041
- typeFilterInput.addEventListener('input', renderResults)
1042
1176
  caseSensitiveInput.addEventListener('change', renderResults)
1043
1177
  regexSearchInput.addEventListener('change', renderResults)
1044
- hasEnumOnlyInput.addEventListener('change', renderResults)
1045
1178
  typeMatchModeInput.addEventListener('change', renderResults)
1179
+ schemaRowsMatchOnlyInput.addEventListener('change', renderResults)
1046
1180
 
1047
1181
  selectAllFieldsBtn.addEventListener('click', () =>
1048
1182
  setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id))),