Add side panels, task selection, graph animation, and project docs

- Foldable left panel (user profile) and right panel (task details)
- Clicking a task in the list or graph node selects it and shows details
- Both views (task list + graph) always mounted via absolute inset-0 for
  correct canvas dimensions; tabs toggle visibility with opacity
- Graph node selection animation: other nodes repel outward (charge -600),
  then selected node smoothly slides to center (500ms cubic ease-out),
  then charge restores to -120 and graph stabilizes
- Graph re-fits on tab switch and panel resize via ResizeObserver
- Fix UUID string IDs throughout (backend returns UUIDs, not integers)
- Add TaskDetailPanel, UserPanel components
- Add CLAUDE.md project documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvis
2026-04-08 11:23:06 +00:00
parent 5c7edd4bbc
commit f1d51b8cc8
23998 changed files with 3242708 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
INTERCEPTION_ROUTE_MARKERS: null,
extractInterceptionRouteInformation: null,
isInterceptionRouteAppPath: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
INTERCEPTION_ROUTE_MARKERS: function() {
return INTERCEPTION_ROUTE_MARKERS;
},
extractInterceptionRouteInformation: function() {
return extractInterceptionRouteInformation;
},
isInterceptionRouteAppPath: function() {
return isInterceptionRouteAppPath;
}
});
const _apppaths = require("../../../shared/lib/router/utils/app-paths");
const INTERCEPTION_ROUTE_MARKERS = [
"(..)(..)",
"(.)",
"(..)",
"(...)"
];
function isInterceptionRouteAppPath(path) {
// TODO-APP: add more serious validation
return path.split("/").find((segment)=>INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m))) !== undefined;
}
function extractInterceptionRouteInformation(path) {
let interceptingRoute, marker, interceptedRoute;
for (const segment of path.split("/")){
marker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
if (marker) {
[interceptingRoute, interceptedRoute] = path.split(marker, 2);
break;
}
}
if (!interceptingRoute || !marker || !interceptedRoute) {
throw new Error(`Invalid interception route: ${path}. Must be in the format /<intercepting route>/(..|...|..)(..)/<intercepted route>`);
}
interceptingRoute = (0, _apppaths.normalizeAppPath)(interceptingRoute) // normalize the path, e.g. /(blog)/feed -> /feed
;
switch(marker){
case "(.)":
// (.) indicates that we should match with sibling routes, so we just need to append the intercepted route to the intercepting route
if (interceptingRoute === "/") {
interceptedRoute = `/${interceptedRoute}`;
} else {
interceptedRoute = interceptingRoute + "/" + interceptedRoute;
}
break;
case "(..)":
// (..) indicates that we should match at one level up, so we need to remove the last segment of the intercepting route
if (interceptingRoute === "/") {
throw new Error(`Invalid interception route: ${path}. Cannot use (..) marker at the root level, use (.) instead.`);
}
interceptedRoute = interceptingRoute.split("/").slice(0, -1).concat(interceptedRoute).join("/");
break;
case "(...)":
// (...) will match the route segment in the root directory, so we need to use the root directory to prepend the intercepted route
interceptedRoute = "/" + interceptedRoute;
break;
case "(..)(..)":
// (..)(..) indicates that we should match at two levels up, so we need to remove the last two segments of the intercepting route
const splitInterceptingRoute = interceptingRoute.split("/");
if (splitInterceptingRoute.length <= 2) {
throw new Error(`Invalid interception route: ${path}. Cannot use (..)(..) marker at the root level or one level up.`);
}
interceptedRoute = splitInterceptingRoute.slice(0, -2).concat(interceptedRoute).join("/");
break;
default:
throw new Error("Invariant: unexpected marker");
}
return {
interceptingRoute,
interceptedRoute
};
}
//# sourceMappingURL=interception-routes.js.map