Compare commits

...
Author SHA1 Message Date
KaifAhmad1andClaude Sonnet 4.6 baa74c6a8c feat(explorer): add welcome screen and fix root path Invalid path error
- Add WelcomeScreen shown on app load; SKE brand button navigates back
- Fix serve_spa: empty root path was hitting dot-guard returning 400
  Invalid path instead of index.html or a welcome JSON response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 15:35:33 +05:30
2 changed files with 43 additions and 6 deletions
+28 -3
View File
@@ -15,7 +15,7 @@ const EntityResolutionTab = lazy(() => import('./workspaces/EnrichWorkspace/Enti
const KGOverviewTab = lazy(() => import('./workspaces/ManageWorkspace/KGOverviewTab').then((module) => ({ default: module.KGOverviewTab })));
const OntologySummaryTab = lazy(() => import('./workspaces/ManageWorkspace/OntologySummaryTab').then((module) => ({ default: module.OntologySummaryTab })));
type WorkspaceId = 'explore' | 'analyze' | 'decisions' | 'enrich' | 'manage';
type WorkspaceId = 'welcome' | 'explore' | 'analyze' | 'decisions' | 'enrich' | 'manage';
type ExploreView = 'graph' | 'vocabulary';
type AnalyzeView = 'sparql' | 'reasoning';
type EnrichView = 'import' | 'merge' | 'registry' | 'resolve';
@@ -311,14 +311,39 @@ function WorkspaceFallback() {
return <div className="workspace-loading">Loading workspace</div>;
}
function WelcomeScreen() {
return (
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 12,
color: 'var(--text-muted)',
}}>
<h1 style={{ margin: 0, fontSize: 28, fontWeight: 700, color: 'var(--text-main)', letterSpacing: '-0.03em' }}>
Welcome to Semantica
</h1>
<p style={{ margin: 0, fontSize: 14 }}>
Select a workspace from the sidebar to get started.
</p>
</div>
);
}
export default function App() {
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceId>('explore');
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceId>('welcome');
const [exploreView, setExploreView] = useState<ExploreView>('graph');
const [analyzeView, setAnalyzeView] = useState<AnalyzeView>('reasoning');
const [enrichView, setEnrichView] = useState<EnrichView>('import');
const [manageView, setManageView] = useState<ManageView>('lineage');
const renderWorkspace = () => {
if (activeWorkspace === 'welcome') {
return <WelcomeScreen />;
}
if (activeWorkspace === 'explore') {
return (
<WorkspaceShell
@@ -451,7 +476,7 @@ export default function App() {
<style>{shellStyles}</style>
<div className="app-shell">
<aside className="app-rail">
<div className="brand-pill" title="Semantica Knowledge Explorer">SKE</div>
<button className="brand-pill" title="Semantica Knowledge Explorer" onClick={() => setActiveWorkspace('welcome')} style={{ cursor: 'pointer', border: '1px solid rgba(127,208,255,0.18)' }}>SKE</button>
{navItems.map(({ id, label, hint, icon: Icon }) => (
<button
key={id}
+15 -3
View File
@@ -188,10 +188,22 @@ async def serve_spa(full_path: str):
if full_path.startswith("api/"):
raise HTTPException(status_code=404, detail="API route not found")
# Root path — serve index.html if built, otherwise a welcome JSON response
if full_path in ("", "/"):
index_file = STATIC_DIR / "index.html"
if index_file.is_file():
return FileResponse(index_file)
return JSONResponse({
"name": "Semantica Knowledge Explorer",
"version": __version__,
"message": "Welcome to Semantica. The frontend is not built yet — run `npm run build` inside the explorer/ directory, or open the Vite dev server at http://localhost:5173.",
"docs": "/docs",
"health": "/health",
})
normalized_path = os.path.normpath(full_path)
if (
normalized_path in ("", ".")
or os.path.isabs(normalized_path)
os.path.isabs(normalized_path)
or normalized_path == ".."
or normalized_path.startswith(".." + os.sep)
):
@@ -200,7 +212,7 @@ async def serve_spa(full_path: str):
# Ensure join remains relative to STATIC_DIR even if input includes leading separators
safe_rel_path = normalized_path.lstrip("/\\")
rel_parts = Path(safe_rel_path).parts
if any(part in ("", ".", "..") for part in rel_parts):
if any(part in (".", "..") for part in rel_parts):
raise HTTPException(status_code=400, detail="Invalid path")
static_dir_resolved = STATIC_DIR.resolve()