diff --git a/frontend/src/client/projects-detail.ts b/frontend/src/client/projects-detail.ts
index 2b0de82..6619a58 100644
--- a/frontend/src/client/projects-detail.ts
+++ b/frontend/src/client/projects-detail.ts
@@ -1,6 +1,7 @@
import { initI18n, onLangChange, t, tDyn, getLang, translateEvent } from "./i18n";
import { initSidebar } from "./sidebar";
import { initNotes } from "./notes";
+import { initProjectTree, refreshProjectTree, rerenderProjectTree } from "./project-tree";
import {
loadParentCandidates,
initParentPicker,
@@ -206,8 +207,9 @@ let deadlines: Deadline[] = [];
let appointments: Appointment[] = [];
let ancestors: ProjectMini[] = [];
let children: ProjectMini[] = [];
-type TreeNode = ProjectMini & { children?: TreeNode[] };
-let projectTree: TreeNode[] = [];
+// projects-cards' /projects tree owns rendering inside the Projektbaum
+// tab too — see initProjectTreeTab below. Local children[] still feeds
+// the empty-state gate + the create-link parent_id pre-fill.
let teamMembers: ProjectTeamMember[] = [];
// t-paliad-139 — additional Team-tab sections.
let descendantStaffed: ProjectTeamMember[] = [];
@@ -1553,15 +1555,15 @@ async function loadChildren(id: string) {
} catch {
children = [];
}
- // Full visible tree drives the rendering.
- try {
- const resp = await fetch(`/api/projects/tree?subtree_counts=false`);
- if (resp.ok) projectTree = ((await resp.json()) as TreeNode[]) ?? [];
- } catch {
- projectTree = [];
- }
}
+// renderChildren is the Projektbaum tab's mount point. m's 2026-05-08
+// 21:28: "should just be the same as the Tree in Projects. It has
+// symbols, everything." Reuse the /projects tree component
+// (project-tree.ts) verbatim — type icons, pin stars, deadline badges,
+// expand/collapse, search highlighting all come along for free. The
+// current project is highlighted via a CSS modifier we add to its
+// data-id row after the tree mounts.
function renderChildren() {
const root = document.getElementById("project-tree")!;
const empty = document.getElementById("children-empty")!;
@@ -1569,30 +1571,22 @@ function renderChildren() {
// CTA next to the empty message is "create sub-project", which would be
// misleading if the tree itself has other branches.
empty.style.display = children.length ? "none" : "";
- if (!projectTree.length) {
- root.innerHTML = "";
- return;
- }
- const currentId = project?.id ?? "";
- root.innerHTML = `
${projectTree.map((n) => renderTreeNode(n, currentId, 0)).join("")}
`;
-}
-
-function renderTreeNode(node: TreeNode, currentId: string, depth: number): string {
- const isCurrent = node.id === currentId;
- const kids = node.children ?? [];
- const childHTML = kids.length
- ? `${kids.map((k) => renderTreeNode(k, currentId, depth + 1)).join("")}
`
- : "";
- const itemClass = `projekt-tree-item${isCurrent ? " projekt-tree-item--current" : ""}`;
- const linkInner =
- `${esc(tDyn("projects.type." + node.type) || node.type)}` +
- `${esc(node.title)}` +
- (node.reference ? `${esc(node.reference)}` : "") +
- `${esc(tDyn("projects.filter.status." + node.status) || node.status)}`;
- const row = isCurrent
- ? `${linkInner}`
- : `${linkInner}`;
- return `${row}${childHTML}`;
+ // Mount the shared tree. initProjectTree fetches /api/projects/tree on
+ // first call and caches; subsequent tab-switches re-render from cache.
+ // Set aria-current on the row matching this project — the shared tree
+ // already styles aria-current=true with a lime highlight (global.css
+ // .projekt-tree-node[aria-current="true"] > .projekt-tree-row).
+ void initProjectTree(root).then(() => {
+ const currentId = project?.id ?? "";
+ if (!currentId) return;
+ root.querySelectorAll(".projekt-tree-node").forEach((li) => {
+ if (li.dataset.id === currentId) {
+ li.setAttribute("aria-current", "true");
+ } else {
+ li.removeAttribute("aria-current");
+ }
+ });
+ });
}
function initChildAddLink() {
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
index 3ea2179..5365a96 100644
--- a/frontend/src/styles/global.css
+++ b/frontend/src/styles/global.css
@@ -9748,63 +9748,13 @@ dialog.quick-add-sheet::backdrop {
opacity: 0.55;
}
-/* Projektbaum / Project Tree — full visible hierarchy on /projects/. */
-.projekt-tree {
- margin-top: 0.5rem;
-}
-
-.projekt-tree-list {
- list-style: none;
- padding-left: 0;
- margin: 0;
-}
-
-.projekt-tree-list .projekt-tree-list {
- padding-left: 1.5rem;
- border-left: 1px dashed var(--color-border-subtle, #e4e4e7);
- margin-left: 0.6rem;
-}
-
-.projekt-tree-item {
- margin: 0.15rem 0;
-}
-
-.projekt-tree-link {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.25rem 0.5rem;
- border-radius: 6px;
- text-decoration: none;
- color: var(--color-text, var(--hlc-midnight));
- line-height: 1.3;
-}
-
-.projekt-tree-link:hover,
-.projekt-tree-link:focus-visible {
- background: var(--color-bg-subtle);
-}
-
-.projekt-tree-link--current {
- background: var(--color-cta-lime-soft, #f0fad9);
- border: 1px solid var(--color-cta-lime, #c6f41c);
- font-weight: 600;
- cursor: default;
-}
-
-.projekt-tree-link--current:hover {
- background: var(--color-cta-lime-soft, #f0fad9);
-}
-
-.projekt-tree-title {
- font-weight: 500;
-}
-
-.projekt-tree-ref {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.85em;
- color: var(--color-text-muted);
-}
+/* Projektbaum tab inside /projects/ reuses the /projects tree
+ component (client/project-tree.ts) — see initProjectTree(root) in
+ client/projects-detail.ts. The earlier inline mini-tree CSS at this
+ block was dead code that also accidentally overrode .projekt-tree-title,
+ so it was removed when m's "should be the same as the Tree in
+ Projects" landed (2026-05-08 21:28). The current-node highlight
+ already lives at .projekt-tree-node[aria-current="true"] above. */
@media (max-width: 640px) {
.projekt-breadcrumb {