GoogleCloudPlatform / GoogleCloudPlatform/knowledge-catalog
visualize: percent-encoded link targets are never decoded — bundles with spaces in filenames render with 0 edges
- Dominant language
- TypeScript
- Stars
- 9.2k
- Forks
- 782
- Avg merge
- 6h 36m
- Merged PRs (30d)
- 85
Description
## Summary
`_extract_links` in `okf/src/reference_agent/viewer/generator.py` never percent-decodes link targets, so any cross-link whose target is percent-encoded — the only CommonMark-conformant way to link to a file whose name contains a space — fails to match its concept and produces **no edge**. A bundle whose filenames contain spaces therefore renders as a fully disconnected node cloud, even when every link is well-formed and every target exists.
This is distinct from #48 (absolute `/`-prefixed links being skipped): the two bugs compound, but applying the #48 fix alone (e.g. PR #184) still yields 0 edges for percent-encoded targets. It is also the consumer-side counterpart of #112 / PR #186, whose proposed SPEC §5.4 says consumers resolving against the filesystem SHOULD percent-decode before matching — the reference viewer currently doesn't.
## Repro (2 files)
`repro-bundle/concepts/Customer Orders.md`:
```markdown
---
type: Reference
title: Customer Orders
---
See [Join Key](/concepts/Join%20Key.md).
```
`repro-bundle/concepts/Join Key.md`:
```markdown
---
type: Reference
title: Join Key
---
Target concept.
```
```
python -m reference_agent visualize --bundle ./repro-bundle
```
Expected: 2 concepts, 1 edge. Actual: `Wrote 2 concept(s), 0 edge(s), …`
Varying only the link in `Customer Orders.md` (verified at d44368c):
| link in `Customer Orders.md` | edges | why |
|---|---|---|
| `[Join Key](Join%20Key.md)` | **0** | resolved as the literal path `Join%20Key.md`; derived id `Join%20Key` never matches the concept id `Join Key` |
| `[Join Key](/concepts/Join%20Key.md)` | **0** | skipped as absolute (#48) **and** not decoded |
| `[Join Key](Join Key.md)` | **0** | `_LINK_RE` forbids whitespace, so the link is never extracted |
| control: `[join-key](join-key.md)` (no spaces anywhere) | **1** | |
Row 2 stays at 0 even with PR #184's absolute-link resolution applied (verified by patching `_extract_links` accordingly): decoding is a separate, unaddressed step.
## Root cause
In `okf/src/reference_agent/viewer/generator.py`:
- `_extract_links` (L48–L66) resolves the raw regex match against the filesystem without `urllib.parse.unquote`, so a percent-encoded target is looked up as a literal path and the derived id can never match the concept id that `_walk_concepts` derives from the (decoded) filename.
- L54 `if "://" in target or target.startswith("/"): continue` additionally drops all absolute bundle-relative links — already tracked in #48, listed here only because the two together are what take a real bundle to zero.
- L12 `_LINK_RE = re.compile(r"\]\(([^)\s]+\.md)(?:#[A-Za-z0-9_\-]*)?\)")` cannot match a destination containing a raw space. Per CommonMark a destination with spaces must be wrapped in `<…>` or percent-encoded, so this is secondary — but the regex supports neither, leaving no working way at all to link to a file with a space in its name.
## Real-world impact
A 166-concept bundle using the SPEC §5.1-recommended absolute form with percent-encoded path segments (874 cross-links in total) renders with **0 edges** — the graph view degrades to an unconnected node cloud precisely for bundles that follow the spec's recommended link style.
## Suggested fix
Decode the target before resolving, alongside the absolute-link resolution from #48/#184:
```python
from urllib.parse import unquote
# in _extract_links:
target = unquote(m.group(1))
```
If PR #186's SPEC §5.4 lands, the reference viewer — as the format's reference consumer — should demonstrate exactly that behavior. A regression test in `okf/tests/test_viewer.py` (e.g. `test_percent_encoded_links_become_edges`, asserting 1 edge for the 2-file bundle above) would pin it.
Happy to send a PR if useful.
Contributor guide
Research direction
Start with _extract_links in okf/src/reference_agent/viewer/generator.py and reproduce the two-file bundle using `python -m reference_agent visualize --bundle ./repro-bundle`. Add the regression coverage described in okf/tests/test_viewer.py, then verify that the percent-encoded target produces one edge for two concepts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data-visualization
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100