The set-state-in-effect refactor inlined each initial-fetch effect as a
standalone `fetchInitial`, duplicating the logic of the existing
reload/fetchOverview/fetchRegistry/loadVersions callbacks instead of
reusing them (required, since eslint-plugin-react-hooks v7 flags calling
an outside setState-touching function directly from an effect body, even
through an async gap - verified via a local lint probe). The duplicates
dropped the setError/flashMsg calls the originals had, so a failed
initial page load in AlignmentsTab, KGOverviewTab, OntologyManager, and
VersionsTab now failed silently instead of showing an error - a
regression of the exact bug #767/#790 fixed for these same files.
Also fixes LineageDiagram only clearing nodes/edges when the new
activeId was falsy, leaving the previous lineage view's stale diagram
on screen while switching directly between two ids.
Previously retryCount never reset on success (removed in adb4613 to
avoid resetting mid-Suspense-fallback), so unrelated transient errors
across a session could permanently exhaust the 3-retry budget even
though each prior retry had actually recovered. Now the counter resets
via a short settle timer after a retry stays error-free, avoiding both
the premature-reset and never-reset failure modes.
KGOverviewTab dropped the nodes-fetch 207 warning whenever stats also
returned 207; HealthTab and AlignmentsTab still had the exact
silent-swallow pattern this PR set out to fix elsewhere in the same
folder. Also documents all of #790's fixes in the changelog.
Ensures network errors, API failures, and 207 Partial Success statuses are surfaced correctly to the user instead of failing silently in the frontend UI.
- OntologyManager: remove red error banner on HTTP 500; always fall back
to empty state silently (error banners reserved for user actions only)
- AlignmentsTab: remove offline-backend warning when both registry and
alignments requests fail; show empty form silently
- ShaclStudio: fix Monarch tokenizer crash — [@] character class prevents
Monaco from misinterpreting @prefix/@base as language-property refs;
wrap beforeMount in try/catch so any Monaco setup failure cannot crash
the React tree
Decision workspace:
- Add AbortController per loadChain() call; abort previous request when a
new decision is selected, preventing stale out-of-order chain responses
- Guard all setState calls with signal.aborted so unmounted component
state updates are skipped; cancel in-flight request on unmount via a
dedicated cleanup effect
SPARQL workspace:
- Guard results table on both result.rows && result.columns to prevent
runtime crash when backend omits columns field
- Use (result.columns ?? []) inside rows.map() to satisfy TypeScript
narrowing inside the closure
- Add .catch() to clipboard.writeText() — silently swallows permission
errors (query remains visible in the editor as fallback)
- Fix CSV export anchor: append to body before click, remove after, to
ensure cross-browser compatibility
Import/Export workspace:
- Fix download anchor: append to document.body before a.click() and
remove afterwards, matching the standard compatible pattern
Lineage workspace:
- Replace 🔗 emoji empty-state icon with lucide-react Link2 for
consistent theming and sizing
Diff & Merge workspace:
- Add "Sample preview" banner above the mock diff table so users know
the displayed fields are illustrative until the backend is connected
OntologyManager:
- Restore non-blocking warning (flash message) when HTTP response is
non-OK and not a 404; network errors (backend down) stay silent
AlignmentsTab:
- When both registry and alignments promises reject, surface a soft
error banner so users know data is missing rather than just empty
Backend (semantica/explorer/routes/ontology.py):
- suggest-alignments: add TF-IDF character-ngram embeddings via sklearn
(SimilarityCalculator-compatible cosine scoring) so embedding_similarity
is populated in results; combined score = 0.4*label + 0.6*embedding when
available, falling back to label-only when sklearn is absent
- suggest-alignments: add token-overlap prefilter before SequenceMatcher so
zero-Jaccard pairs are skipped without computing full similarity; add
_MAX_ENTITIES_PER_SIDE=500 per-ontology cap on top of the existing
_MAX_ANALYSIS_NODES global cap
- suggest-alignments: remove dead try/except OntologyEngine.create_alignment
block that always failed silently (no TripletStore configured); replace
with a comment explaining the intentional ephemeral-only storage model
- health: replace O(alignments x entities) any() scans for alignment coverage
with O(1) set membership checks via assessed_ids
- shacl/validate: run rdflib.Graph().parse(format='turtle') syntax check on
the submitted Turtle before returning; invalid syntax now raises 422 instead
of returning a misleading unavailable/success response
Frontend:
- AlignmentsTab: add pairwise alignment matrix section that groups recorded
alignments by (source_ontology, target_ontology) pair; each cell shows
color-coded relation badges per RELATION_COLORS; clicking a badge populates
the create/edit form for quick editing; matrix is shown when at least two
ontologies are loaded
- ShaclStudio: add selectedShapeId state and fullShacl ref; each shape row in
the library is now a clickable button that extracts its Turtle block from
the full SHACL and pre-populates the Monaco editor; a "View all" toggle
restores the full SHACL; selected shape ID is shown in the editor header
- GraphWorkspace: fix viewMode race in external focus effect — call
setSelectedNodeId directly instead of going through focusNode(), which
captured a stale viewMode in its closure; remove focusNode from the
dependency array since it is no longer called
Tests (14 passing, was 11):
- Add test_suggest_alignments_returns_embedding_similarity: asserts
embedding_similarity is non-null when sklearn is available
- Add test_shacl_validate_rejects_invalid_turtle_syntax: asserts 422 on
syntactically invalid Turtle
- Add test_health_alignment_coverage_uses_set_lookup: asserts alignment
dimension score is non-zero after recording an alignment, verifying the
O(1) set lookup path works correctly end-to-end
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Backend:
- Replace false conforms=True SHACL stub with status=unavailable always;
live validation cannot be wired until OntologyEngine.validate_graph is
connected to a data graph — a stub that returns conforms=True misleads
users editing shapes
- Cap node/edge fetches in health, suggest-alignments, and SHACL generation
at _MAX_ANALYSIS_NODES (5 000) with a logger.warning when the graph
exceeds the limit; unbounded limit=999_999 fetches cause OOM on large graphs
- Set SHACL health dimension score to 0.0 (was 70.0) when status=unavailable;
exclude unavailable dimensions from the total_score average so they neither
inflate nor deflate the result
- Allow alignments to reference external/unloaded URIs (e.g. schema.org)
without raising 404; label falls back to URI fragment or caller-supplied
source_label/target_label fields added to OntologyAlignmentRequest
- Fix _alignment_id to use uuid.NAMESPACE_OID instead of NAMESPACE_URL;
the composite key is not a URL
- Fix _summarize_shapes to normalise \r\n before splitting on .\n so shape
parsing works correctly on Windows line endings
Frontend:
- Wrap handleSave/handleSuggest/handleRemove/handleAcceptSuggestion in
useCallback in AlignmentsTab for consistency with sibling components
- Add ephemeral-storage banner in AlignmentsTab warning that alignments are
session-memory-only and not persisted across restarts
- Fix exportReport in HealthTab to append/remove anchor from document before
clicking and defer URL.revokeObjectURL to avoid Blob URL leak in some browsers
- Derive health dimension grid column count from health.dimensions.length
instead of the hardcoded repeat(5, ...) that breaks if the backend adds
or removes a dimension
- Add minimal Monarch tokenizer for the Monaco turtle language registration
in ShaclStudio so prefix declarations, IRIs, SHACL properties, comments,
and string literals are syntax-highlighted; previously the editor rendered
as plain text despite theme rules being defined
Tests (11 passing, was 5):
- Rename test_shacl_validate_has_stable_contract to
test_shacl_validate_returns_unavailable and assert status == unavailable
- Add test_shacl_validate_rejects_empty_turtle (expects 422)
- Add test_health_returns_404_for_unknown_ontology
- Add test_health_shacl_dimension_is_zero_when_unavailable with total_score check
- Add test_delete_unknown_alignment_returns_404
- Add test_alignment_upsert_is_idempotent (verifies ID stability and created_at
preservation across updates)
- Add test_alignment_accepts_external_uri (verifies no 404 for schema.org URIs)
- Relax test_alignment_suggestions_are_ranked label assertions to substring
checks so the test survives similarity algorithm changes
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Bug 4 — Upload format misdetected:
- Added xml→'xml' and json→'json-ld' to the extension→format map so
.xml and .json files are no longer misidentified as turtle.
- Changed the fallback from '|| "turtle"' to '?? ""' (empty string for
unknown extensions) so the backend _detect_format() runs instead of
blindly assuming turtle for any unrecognised extension.
- Omit the format key entirely from the load request body when no format
was detected, letting the backend auto-detect from content heuristics.
- Added .n3 to the file picker accept list and dropzone hint text.
Bug 1 — Broken registry filters:
fetchRegistry no longer sends format/kind values (owl/skos/internal/external)
as the status query param; those filters are applied client-side via
filteredEntries which already had the correct logic. Only the text search
param q is delegated to the backend.
Bug 2 — Toggle/refresh URI corruption:
Removed removesuffix('/toggle') and removesuffix('/refresh') from
toggle_ontology and refresh_ontology. Starlette's route regex already
strips the literal suffix from the captured path param; the removesuffix
call was a no-op for normal URIs but corrupted any ontology URI that
legitimately ends with /toggle or /refresh.
Bug 3 — SSRF in URL fetch:
Added _validate_fetch_url() which rejects non-http/https schemes and
resolves the hostname to block private, loopback, link-local, reserved,
and multicast addresses before requests.get() is called. Applied to all
three fetch sites: preview, load, and refresh.
Bug 5 — Inconsistent XML hardening:
_parse_rdf_sync now calls _safe_parse_rdf() from
semantica/explorer/utils/rdf_parser.py instead of g.parse() directly,
applying the existing defusedxml-based XXE protection for RDF/XML inputs.
Bug 6 — Search scans whole graph:
search_entities now calls session.search(q, limit*6) which hits the
GraphSearchIndex instead of fetching up to 999,999 nodes and doing a
linear Python substring scan. Results are post-filtered by _SEARCHABLE_TYPES
and entity_type before being returned up to the requested limit.
- Replace invalid inset-left with inset: 0 0 0 72px on ::before at <=680px
- Add matching mobile inset fix to ::after (was still at 88px)
- Merge duplicate .landing-capability-band CSS rule blocks into one
- Fix non-standard font-weight: 850 -> 800 on .landing-launcher-item-title
- Remove unused eyebrow field from LandingAction type and all data entries
- Extract static 42-dot SVG preview array to module-level PREVIEW_DOTS constant
Co-Authored-By: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <kaifahmad087@gmail.com>
- Fix dead `if (anchorNodeId)` conditional in buildHeatmapRenderSnapshot
(anchor is always truthy past the early-return guard on line 263)
- Replace O(n) array .includes() with WeakMap-cached Set.has() in
resolveDistanceNodeStyle heatmap path — prevents per-node O(n) scan
during every Sigma reducer pass on large graphs
- Rename GraphDistanceBucketCounts.threeHop → threeHopPlus across
types.ts, graphSceneState.ts, and GraphWorkspace.tsx so the field
name reflects that it accumulates distance ≥ 3, not exactly 3;
update status-strip labels to "3+ hop" accordingly
- Restore hasMetrics guard in PathDistanceIntelPanel to suppress the
empty metric grid <div> when a path result carries no optional metrics
Co-Authored-By: ZohaibHassan16 <zohaib@hawksight.ai>
Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
- Extract ENTITY_SHAPE_ALIASES and classifyEntityShape into a shared
graphEntityShape.ts utility — resolveEntityShape was duplicated with
divergent signatures in useLoadGraph.ts and graphSceneState.ts; both
now import from one place so aliases can never drift
- graphSceneState.resolveEntityShape falls back to classifyEntityShape
for nodes created programmatically that bypass useLoadGraph
- Fix graphTheme.ts indentation around fullGraphStructure,
fullGraphStructureLayer, and interaction — closing braces were at
wrong indent levels making the nesting visually misleading
- Add comment on fullGraphStructureLayer.mode explaining it is
intentionally "off" as a staged-rollout gate (flip to "auto" to enable
cross-community canvas curve rendering)
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
Merge origin/main (Distance Intelligence #502) into feat/explorer-visual-refresh.
Conflict was in the viewModeItems useMemo: the PR's new cluster-based toolbar
structure diverged from main's coreToolbarGroups additions.
Resolution:
- Keep PR's viewModeItems as a clean 3-item segmented control (Full/Grouped/Focused)
- Port Distance Intelligence controls (ego mode, heatmap, structural/semantic overlay)
into a new distanceToolbarItems useMemo that slots into the cluster toolbar as a
"Distance" cluster, visible only when a node is selected
- Wire distanceToolbarItems into toolbarClusters between "local-structure" and
"analysis" clusters
- All other Distance Intelligence additions (state vars, BFS helpers, useEffects,
ego depth slider, GraphInspectorPanel onFocusNode prop) merged cleanly
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
- Fix `import.meta.env.DEV` crash in graphSceneState.ts that broke the
entire test:graph-workspace suite (module load fails in Node.js/tsx)
- Export `resolveGroupedDisplayNodeId` from graphSceneState.ts and
remove the identical copy in GraphWorkspace.tsx
- Add `checkGroupedViewAvailability` helper (Louvain only, no centrality)
so grouped view availability can be checked cheaply on every graph change
- Gate full community graph build (`groupedDisplayCandidate`) on
`viewMode === 'grouped'` to avoid running Louvain + centrality on every
graph version tick when the user is not in grouped view
- Remove dead ternary in `focusNode` where both branches returned `nodeId`
- Add 7 new tests covering resolveGroupedDisplayNodeId,
resolveGroupedDisplayStateSnapshot, and checkGroupedViewAvailability
Co-Authored-By: ZohaibHassan16 <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
Merge conflict resolution:
- Kept fix/graph-motion's conditional layout-stop (only in focused mode)
to preserve live layout motion for derived graphs — the core intent of
this PR.
Must-fix items resolved:
1. plugin.json — removed "hooks": "./hooks/hooks.json" (re-added by this
branch, already removed in PR #489 on main as it is auto-loaded).
Kept "agents": "./agents".
2. Double Louvain per render — added groupedViewAvailable useMemo in
GraphWorkspace (deps: [graphVersion]) that runs community detection
once. Passed result into resolveDisplayGraph and resolveDisplayStateSnapshot
via new groupedViewAvailable option; both functions skip their internal
computeGraphAnalyticsBase call when the value is pre-supplied.
3. graphVersion in displayState deps — removed graphVersion from the
displayState memo dep array. displayState now depends on the stable
boolean groupedViewAvailable, not on every ADD_NODE/ADD_EDGE tick,
so Louvain no longer re-fires on every WebSocket update.
4. hideLabelsOnMove / hideEdgesOnMove flipped to true — intentional:
suppressing labels and edges during pan reduces visual noise and is
part of the flicker-reduction fix described in the PR.
Co-authored-by: ZohaibHassan16 <zohaibhassan1696@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
1. Prevent active-but-disabled Focused button by only disabling when
viewMode is not already "focused" (viewMode !== "focused" && !canActivateFocusedMode).
2. Generalize inspector fallback copy — stale/invalid node IDs are not
necessarily grouped items, so remove the misleading "Activate Focused
mode" hint.
3. Move pluginRuntimeRef.current read out of render by converting
canActivateFocusedMode from useMemo to useState + useEffect, resolving
two ESLint "cannot access refs during render" errors and the missing
toolbar-memo dependency warning.
Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>