• Joined on 2026-01-29

@mai/projax-ui (0.1.1)

Published 2026-09-27 13:47:13 +00:00 by mAi

Installation

@mai:registry=https://mgit.msbls.de/api/packages/mAi/npm/
npm install @mai/projax-ui@0.1.1
"@mai/projax-ui": "0.1.1"

About this package

Svelte 5 screens for projax, on @mai/mkit's dataview and shell.

projax-ui

Svelte 5 screens for projax — the tree, board and timeline views that mount on @mai/mkit's DataView, DataBoard and Tree (m/mkit#242, item 6 of m/projax#40's ordered work: docs/plans/projax-as-a-module.md). The tree screen (m/mkit#243, item 7) is the first one to land.

A standalone package rather than a @mai/mkit subpath, so a projax-only change never bumps mBrian, paliad, mWiki or the HLC hub, none of which mount projax (m/mkit#242, d-8f030a3a, d-a91a1412). The -ui in the name keeps it clear of the Go data package m/projax ships under d-8f030a3a.

Install

Published as @mai/projax-ui on this Gitea instance's own npm registry, under the mAi account's namespace, the account that cuts every release.

bun add @mai/projax-ui @mai/mkit

Point the @mai scope at the registry in a .npmrc (project root or user level):

@mai:registry=https://mgit.msbls.de/api/packages/mAi/npm/

The package is readable without a credential. @mai/mkit is a peer dependency, not a regular one: install it alongside, at the version this package's peerDependencies names. A consumer that instead lets a nested copy resolve — mismatched kit versions inside one app — ends up with two kit stores, seen in mBrian at @mai/mkit 0.2.7 beside @mai/meditor 0.9.0.

Versioning

d-880df9d8, m's rule for this workspace, also in CHANGELOG.md's header: 0.x for now, a patch for every fix and every addition, a minor only for a change a consumer must react to, and 1.0 when m calls the package stable. A branch never names the number; it writes a CHANGELOG.d/ fragment with bump: patch or bump: minor (root README § Publishing).

Use

import { ProjaxTree, ProjaxBoard, ProjaxFields, buildForest, projaxQuery, loadAllRows } from '@mai/projax-ui';

ProjaxTree is the tree screen (m/mkit#243, item 7 of m/projax#40's ordered work), over @mai/mkit/shell's Tree/TreeGroup/TreeRow. It never holds or sends PROJAX_MCP_TOKEN: it takes rows, already fetched — the host's own server load calls projax's GET /api/items with its Bearer token (projaxQuery/loadAllRows build the request and page past the 100-row ceiling) and passes the result in as a prop.

// +page.server.ts, or the equivalent in a host that is not SvelteKit
import { loadAllRows, projaxQuery, ProjaxFields } from '@mai/projax-ui';
import { decodeState } from '@mai/mkit/dataview';

export async function load({ url, fetch }) {
	const state = decodeState(url.search, ProjaxFields);
	const rows = await loadAllRows(
		(query) => fetch(`https://projax.msbls.de/api/items?${query}`, { headers: { Authorization: `Bearer ${PROJAX_MCP_TOKEN}` } }).then((r) => r.json()),
		projaxQuery(state, ProjaxFields)
	);
	return { rows };
}

ProjaxItemCard is the per-item render slot — status chip, tags, management chips, pinned star, archived flag, the multi-parent badge — exported on its own, shared by the tree and the board so neither rebuilds it.

The task and issue rollup

GET /api/items?rollup=true adds each row's own aggregate over CalDAV and Gitea — open tasks, overdue, open issues, next signal, last activity (m/projax#49) — at a measured cold cost of 7.8–8.2 s on the live item set (m/projax#52), against about 0.11 s for a plain page. ProjaxTree and ProjaxBoard therefore take the rollup as a second, separate load, never folded into the fast first one that paints the screen:

// +page.server.ts, or the equivalent in a host that is not SvelteKit
import { loadAllRows, loadRollups, projaxQuery, ProjaxFields } from '@mai/projax-ui';
import { decodeState } from '@mai/mkit/dataview';

export async function load({ url, fetch }) {
	const state = decodeState(url.search, ProjaxFields);
	const loader = (query) => fetch(`https://projax.msbls.de/api/items?${query}`, { headers: { Authorization: `Bearer ${PROJAX_MCP_TOKEN}` } }).then((r) => r.json());
	const rows = await loadAllRows(loader, projaxQuery(state, ProjaxFields));
	// Not awaited: the page renders `rows` at once, and `rollupSet` streams in once the slow call resolves.
	const rollupSet = loadRollups(loader, projaxQuery(state, ProjaxFields, undefined, true));
	return { rows, rollupSet };
}
<!-- +page.svelte -->
<ProjaxTree rows={data.rows} rollupSet={data.rollupSet} />

rollupSet takes either a resolved RollupSet ({rollups, builtAt}, keyed by item id) or a Promise<RollupSet> — a host that already awaited it hands in the plain value, a host that wants the tree to paint before the rollup arrives hands in the promise unawaited, as above. ProjaxTree/ProjaxBoard look each row's rollup up by id and pass it to that row's ProjaxItemCard, which renders nothing extra until its own rollup is present — a plain GET /api/items and the tree's first render are never blocked by it.

ProjaxBoard is the board screen (m/mkit#244, item 8), over @mai/mkit/dataview's DataBoard. It groups the items feed by status, area, tag or management; a drop moves the card at once and hands a {feed, group_by, card_id, from, to} intent to a host-supplied mover — the package holds no PROJAX_MCP_TOKEN and no move semantics, the mover's the loader's own counterpart. A refusal reverts the card and shows the mover's error text as is.

// +page.svelte, or the equivalent in a host that is not SvelteKit
import { ProjaxBoard } from '@mai/projax-ui';

async function mover(intent) {
	const res = await fetch('https://projax.msbls.de/api/board/move', {
		method: 'POST',
		headers: { Authorization: `Bearer ${PROJAX_MCP_TOKEN}`, 'Content-Type': 'application/json' },
		body: JSON.stringify(intent)
	});
	if (res.status === 204) return { ok: true };
	return { ok: false, error: (await res.json()).error };
}
<ProjaxBoard rows={data.rows} groupBy="status" {mover} />

The tasks feed (group_by=due) is not built: ProjaxFields has no due field and this package has no tasks rows source, so grouping items by a due date has nothing to group on. A tasks board waits on both landing first.

The timeline

ProjaxTimeline is the day-grouped spine (m/mkit#252, step 2 of m/projax#40's ordered work), over GET /api/feeds/timeline (m/projax#45 step 1). Same division as ProjaxTree/ProjaxBoard: it takes rows, already fetched, and holds no PROJAX_MCP_TOKEN and does no fetch of its own.

// +page.server.ts, or the equivalent in a host that is not SvelteKit
import { loadAllRows, timelineQuery, TimelineFields } from '@mai/projax-ui';
import { decodeState } from '@mai/mkit/dataview';

export async function load({ url, fetch }) {
	const state = decodeState(url.search, TimelineFields);
	const rows = await loadAllRows(
		(query) => fetch(`https://projax.msbls.de/api/feeds/timeline?${query}`, { headers: { Authorization: `Bearer ${PROJAX_MCP_TOKEN}` } }).then((r) => r.json()),
		timelineQuery(state, TimelineFields)
	);
	return { rows };
}
<!-- +page.svelte -->
<ProjaxTimeline rows={data.rows} getHref={(row) => `/i/${row.item_path}`} />

The feed answers with a flat rows array, each row carrying its own day, day_label and sticky rather than a nested days structure — groupTimelineDays folds that back into the day-grouped shape the screen renders, trusting the feed's own guarantee that a page boundary never splits a day from its header. getHref is optional, same convention as ProjaxTree's: absent, the project name renders as plain text; given, it is a link.

A todo, event or doc row renders through its own kind-specific markup; a creation row reuses ProjaxItemCard, since it names nothing but an item. The package rebuilds no mutation: timeline.tmpl's own complete/edit/delete forms are the host's concern, same division as ProjaxBoard's mover.

timelineQuery adds no f.status/f.archived default the way projaxQuery does for the tree — the feed already defaults to active, non-archived items itself. Its order argument ('asc' | 'desc', default 'desc') writes the wire's dir directly, since the feed reads dir off the raw query regardless of any sort field and defaults to desc itself, the opposite of encodeState's own asc baseline.

Develop

bun install
bun run test:unit # src/*.test.ts
bun run build     # svelte-package into dist/
bun run check     # svelte-check

Dependencies

Development Dependencies

ID Version
@mai/mkit ^0.2.14
@sveltejs/package ^2.3.0
@sveltejs/vite-plugin-svelte ^6.2.4
@types/bun ^1.3.9
publint ^0.3.0
svelte ^5.57.0
svelte-check ^4.0.0
typescript ^5.0.0
vite ^7.3.6

Peer Dependencies

ID Version
@mai/mkit ^0.2.12
svelte ^5.0.0
Details
npm
2026-09-27 13:47:13 +00:00
5
MIT
20 KiB
Assets (1)
Versions (30) View all
0.4.1 2026-10-01
0.4.0 2026-10-01
0.3.0 2026-10-01
0.2.2 2026-09-30
0.2.1 2026-09-30