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,173 @@
import { DOCUMENT_MODE } from '../common/html.js';
export const defaultTreeAdapter = {
//Node construction
createDocument() {
return {
nodeName: '#document',
mode: DOCUMENT_MODE.NO_QUIRKS,
childNodes: [],
};
},
createDocumentFragment() {
return {
nodeName: '#document-fragment',
childNodes: [],
};
},
createElement(tagName, namespaceURI, attrs) {
return {
nodeName: tagName,
tagName,
attrs,
namespaceURI,
childNodes: [],
parentNode: null,
};
},
createCommentNode(data) {
return {
nodeName: '#comment',
data,
parentNode: null,
};
},
createTextNode(value) {
return {
nodeName: '#text',
value,
parentNode: null,
};
},
//Tree mutation
appendChild(parentNode, newNode) {
parentNode.childNodes.push(newNode);
newNode.parentNode = parentNode;
},
insertBefore(parentNode, newNode, referenceNode) {
const insertionIdx = parentNode.childNodes.indexOf(referenceNode);
parentNode.childNodes.splice(insertionIdx, 0, newNode);
newNode.parentNode = parentNode;
},
setTemplateContent(templateElement, contentElement) {
templateElement.content = contentElement;
},
getTemplateContent(templateElement) {
return templateElement.content;
},
setDocumentType(document, name, publicId, systemId) {
const doctypeNode = document.childNodes.find((node) => node.nodeName === '#documentType');
if (doctypeNode) {
doctypeNode.name = name;
doctypeNode.publicId = publicId;
doctypeNode.systemId = systemId;
}
else {
const node = {
nodeName: '#documentType',
name,
publicId,
systemId,
parentNode: null,
};
defaultTreeAdapter.appendChild(document, node);
}
},
setDocumentMode(document, mode) {
document.mode = mode;
},
getDocumentMode(document) {
return document.mode;
},
detachNode(node) {
if (node.parentNode) {
const idx = node.parentNode.childNodes.indexOf(node);
node.parentNode.childNodes.splice(idx, 1);
node.parentNode = null;
}
},
insertText(parentNode, text) {
if (parentNode.childNodes.length > 0) {
const prevNode = parentNode.childNodes[parentNode.childNodes.length - 1];
if (defaultTreeAdapter.isTextNode(prevNode)) {
prevNode.value += text;
return;
}
}
defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(text));
},
insertTextBefore(parentNode, text, referenceNode) {
const prevNode = parentNode.childNodes[parentNode.childNodes.indexOf(referenceNode) - 1];
if (prevNode && defaultTreeAdapter.isTextNode(prevNode)) {
prevNode.value += text;
}
else {
defaultTreeAdapter.insertBefore(parentNode, defaultTreeAdapter.createTextNode(text), referenceNode);
}
},
adoptAttributes(recipient, attrs) {
const recipientAttrsMap = new Set(recipient.attrs.map((attr) => attr.name));
for (let j = 0; j < attrs.length; j++) {
if (!recipientAttrsMap.has(attrs[j].name)) {
recipient.attrs.push(attrs[j]);
}
}
},
//Tree traversing
getFirstChild(node) {
return node.childNodes[0];
},
getChildNodes(node) {
return node.childNodes;
},
getParentNode(node) {
return node.parentNode;
},
getAttrList(element) {
return element.attrs;
},
//Node data
getTagName(element) {
return element.tagName;
},
getNamespaceURI(element) {
return element.namespaceURI;
},
getTextNodeContent(textNode) {
return textNode.value;
},
getCommentNodeContent(commentNode) {
return commentNode.data;
},
getDocumentTypeNodeName(doctypeNode) {
return doctypeNode.name;
},
getDocumentTypeNodePublicId(doctypeNode) {
return doctypeNode.publicId;
},
getDocumentTypeNodeSystemId(doctypeNode) {
return doctypeNode.systemId;
},
//Node types
isTextNode(node) {
return node.nodeName === '#text';
},
isCommentNode(node) {
return node.nodeName === '#comment';
},
isDocumentTypeNode(node) {
return node.nodeName === '#documentType';
},
isElementNode(node) {
return Object.prototype.hasOwnProperty.call(node, 'tagName');
},
// Source code location
setNodeSourceCodeLocation(node, location) {
node.sourceCodeLocation = location;
},
getNodeSourceCodeLocation(node) {
return node.sourceCodeLocation;
},
updateNodeSourceCodeLocation(node, endLocation) {
node.sourceCodeLocation = { ...node.sourceCodeLocation, ...endLocation };
},
};