mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
* fix(editor): bump hast-util-to-html to v9 so lowlight output actually serializes
Source view of fenced ```html (and any other code block falling through to
the lowlight branch in ReadonlyContent) silently rendered as un-highlighted
escaped text. Root cause was a stale dep pin: `hast-util-to-html: ^4.0.1`
predates the package's ESM/named-export rewrite — v4 only exports a CJS
default function, so the `import { toHtml } from "hast-util-to-html"` in
code-block-static.tsx:19 and readonly-content.tsx:32 resolved to
`undefined` at runtime. The try/catch in both call sites caught the
"toHtml is not a function" throw and fell through to escapeHtml plain
text, so no `.hljs-*` spans ever made it to the DOM and the syntax-color
CSS added in #2808 had nothing to attach to.
Bumping to ^9.0.5 (matches the v9 line that lowlight@3 / remark / rehype
ship in the rest of the tree) makes the named `toHtml` export available
and source-view highlighting works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(editor): open HTML attachment in new tab + full-page preview route
Adds a third toolbar button to HtmlAttachmentPreview between Maximize and
Download: open the attachment in a new app tab (desktop) or browser tab
(web). The full-screen modal stays — they serve different scenarios:
modal for a quick "see it bigger" without leaving the issue context,
new-tab when the user wants to keep the rendered HTML around while
working on something else.
Components:
- New workspace path: `/{slug}/attachments/{id}/preview?name={filename}`.
Lives outside the (dashboard) group on web so the iframe gets the full
viewport — sidebar would defeat the point. Desktop registers the route
inside `WorkspaceRouteLayout` so workspace context resolution still
runs (no slug → no path is built).
- `packages/views/attachments/attachment-preview-page.tsx`: shared full-
page view that reuses `useAttachmentHtmlText` for the iframe srcDoc.
Sandbox stays `allow-scripts` (no allow-same-origin) — same security
posture as the inline preview.
- `HtmlAttachmentPreview`: adds Open-in-new-tab button. Routes through
`useNavigation().openInNewTab` when available (desktop), falls back to
`window.open(getShareableUrl(path))` on web. Button is hidden when no
workspace slug is in scope (shouldn't happen in practice, but the
shared component must not throw outside a workspace route).
Tests cover: desktop openInNewTab call args, web window.open fallback,
and that the failure-mode toolbar still surfaces all three actions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(editor): drop now-stale @ts-expect-error on hast-util-to-html imports
v9 ships bundled type declarations, so the directives added for v4 trigger
TS2578 ("Unused '@ts-expect-error' directive") on CI typecheck.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
/**
|
|
* Centralized URL path builder. All navigation in shared packages (packages/views)
|
|
* MUST go through this module — no hardcoded string paths.
|
|
*
|
|
* Two kinds of paths:
|
|
* - workspace-scoped: paths.workspace(slug).xxx() — carry workspace in URL
|
|
* - global: paths.login(), paths.newWorkspace(), paths.invite(id) — pre-workspace routes
|
|
*
|
|
* Why pure functions + builder pattern:
|
|
* - Changing a route shape (e.g. adding workspace slug prefix) becomes a single-file edit
|
|
* - IDs are always URL-encoded here so callers can't forget
|
|
* - Zero runtime deps means this module is safe in Node (tests) and browsers
|
|
*/
|
|
|
|
const encode = (id: string) => encodeURIComponent(id);
|
|
|
|
function workspaceScoped(slug: string) {
|
|
const ws = `/${encode(slug)}`;
|
|
return {
|
|
root: () => `${ws}/issues`,
|
|
usage: () => `${ws}/usage`,
|
|
issues: () => `${ws}/issues`,
|
|
issueDetail: (id: string) => `${ws}/issues/${encode(id)}`,
|
|
projects: () => `${ws}/projects`,
|
|
projectDetail: (id: string) => `${ws}/projects/${encode(id)}`,
|
|
autopilots: () => `${ws}/autopilots`,
|
|
autopilotDetail: (id: string) => `${ws}/autopilots/${encode(id)}`,
|
|
agents: () => `${ws}/agents`,
|
|
agentDetail: (id: string) => `${ws}/agents/${encode(id)}`,
|
|
memberDetail: (id: string) => `${ws}/members/${encode(id)}`,
|
|
squads: () => `${ws}/squads`,
|
|
squadDetail: (id: string) => `${ws}/squads/${encode(id)}`,
|
|
inbox: () => `${ws}/inbox`,
|
|
myIssues: () => `${ws}/my-issues`,
|
|
runtimes: () => `${ws}/runtimes`,
|
|
runtimeDetail: (id: string) => `${ws}/runtimes/${encode(id)}`,
|
|
skills: () => `${ws}/skills`,
|
|
skillDetail: (id: string) => `${ws}/skills/${encode(id)}`,
|
|
settings: () => `${ws}/settings`,
|
|
attachmentPreview: (id: string) => `${ws}/attachments/${encode(id)}/preview`,
|
|
};
|
|
}
|
|
|
|
export const paths = {
|
|
workspace: workspaceScoped,
|
|
|
|
// Global (pre-workspace) routes
|
|
login: () => "/login",
|
|
newWorkspace: () => "/workspaces/new",
|
|
invite: (id: string) => `/invite/${encode(id)}`,
|
|
invitations: () => "/invitations",
|
|
onboarding: () => "/onboarding",
|
|
authCallback: () => "/auth/callback",
|
|
root: () => "/",
|
|
};
|
|
|
|
export type WorkspacePaths = ReturnType<typeof workspaceScoped>;
|
|
|
|
// Prefixes — not slug names — because we match against full URL paths.
|
|
// A path is global if it equals or begins with any of these.
|
|
// Note: `/workspaces/` (trailing slash) is the prefix — `workspaces` is reserved,
|
|
// so any path starting with `/workspaces/...` is system-owned, not user-owned.
|
|
const GLOBAL_PREFIXES = ["/login", "/workspaces/", "/invite/", "/invitations", "/onboarding", "/auth/", "/logout", "/signup"];
|
|
|
|
export function isGlobalPath(path: string): boolean {
|
|
return GLOBAL_PREFIXES.some((p) => path === p || path.startsWith(p));
|
|
}
|