release: v0.2 – collaboration docs, richer demo data, new screenshots
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m30s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m30s
- Turn the collaboration plan into module docs (Doku/modules/collaboration.md) describing the implemented state; remove Doku/plans - Header version comes from the build: git tag via APP_VERSION (CI passes it for tag builds), otherwise "<package version>-dev"; package 0.2.0 - Sample data (setup option and demo mode): six inbox thoughts, two task lists with Kanban states, subtasks and due dates, shopping and packing lists, three notes in folders incl. a long nested outline; demo mode shares the note and two lists with user/user - e2e/screenshots.sh renews the README screenshots from demo data - README: live collaboration, editor sharing, image tags, version badge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f26f286fb8
commit
4e39b367bd
25 files changed
+590
-373
No files matched your search
@@ -37,3 +37,7 @@ Sperren, Präsenz, Rückgängig und Konfliktkopien mit zwei Browsern: Schloss un
|
||||
Der Eigentümer tippt mit normaler Verbindung, der Bearbeiter sitzt hinter einem simulierten Proxy. Für jede Situation wird der gewählte Weg und die Zeit bis zur Anzeige gemessen: nichts blockiert (WebSocket), WebSocket abgelehnt (SSE), WebSocket angenommen aber stumm (SSE nach Herzschlag-Ausfall), WebSocket und SSE blockiert sowie SSE hängend (Long-Polling), nur normale Anfragen (Abfrage alle 3 s).
|
||||
|
||||
`collab.e2e.mjs`, `lock.e2e.mjs` und `channels.e2e.mjs` dauern je etwa eine Minute. Die Tests laufen nicht in der CI, weil sie Browser-Images brauchen.
|
||||
|
||||
## Screenshots
|
||||
|
||||
`e2e/screenshots.sh` baut das Image, startet es im Demo-Modus mit frischer Datenbank und erzeugt die Bilder in `screenshots/` neu (`screenshots.mjs`). Für die Versionsangabe im Kopf: `APP_VERSION=v0.2 e2e/screenshots.sh`. In der Workspace-Aufnahme bearbeitet der Demo-Benutzer `user` gleichzeitig einen Bullet, damit Sperre und Präsenz zu sehen sind.
|
||||
@@ -0,0 +1,84 @@
|
||||
// Takes the README screenshots from a fresh server in demo mode (sample data of admin/admin).
|
||||
// Usage: e2e/screenshots.sh (builds the image) or MINDSIFT_URL=… SHOTS_DIR=… node screenshots.mjs
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const BASE = process.env.MINDSIFT_URL || 'http://localhost:18080';
|
||||
const OUT = process.env.SHOTS_DIR || '../screenshots';
|
||||
const DESKTOP = { viewport: { width: 1518, height: 886 }, deviceScaleFactor: 2 };
|
||||
const MOBILE = { viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true };
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
async function login(user) {
|
||||
const res = await fetch(`${BASE}/api/v1/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ login: user, password: user }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`login ${user}: ${res.status}`);
|
||||
return (await res.json()).token;
|
||||
}
|
||||
|
||||
async function open(browser, token, options) {
|
||||
const ctx = await browser.newContext({ ...options, colorScheme: 'dark', locale: 'de-DE' });
|
||||
await ctx.addInitScript((t) => localStorage.setItem('mindsift_jwt_token', t), token);
|
||||
const page = await ctx.newPage();
|
||||
page.on('pageerror', (e) => console.log('PAGEERROR', e.message));
|
||||
await page.goto(BASE);
|
||||
await page.waitForLoadState('networkidle');
|
||||
return page;
|
||||
}
|
||||
|
||||
const tab = (page, name) => page.getByRole('button', { name: new RegExp(name) }).first().click();
|
||||
|
||||
async function shot(page, file) {
|
||||
await sleep(800); // animations
|
||||
await page.mouse.move(0, 0);
|
||||
await page.screenshot({ path: `${OUT}/${file}` });
|
||||
console.log(`OK ${file}`);
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const admin = await login('admin');
|
||||
const user = await login('user');
|
||||
const browser = await chromium.launch({ args: ['--lang=de-DE'] });
|
||||
|
||||
const page = await open(browser, admin, DESKTOP);
|
||||
await tab(page, 'Triage');
|
||||
await shot(page, 'Triage_desktop.png');
|
||||
|
||||
await tab(page, 'Aufgaben');
|
||||
await page.getByText('Homelab', { exact: true }).first().click().catch(() => {});
|
||||
await page.getByRole('button', { name: /Kanban|Board/ }).first().click().catch(() => {});
|
||||
await shot(page, 'aufgaben_desktop.png');
|
||||
|
||||
// The demo user edits a bullet of the shared note, so the screenshot shows lock and presence
|
||||
const other = await open(browser, user, DESKTOP);
|
||||
await tab(other, 'Workspace');
|
||||
await other.getByText(/^Homelab 2\.0/).first().click();
|
||||
await other.waitForFunction(() => document.querySelectorAll('textarea').length > 5, null, { timeout: 10000 });
|
||||
await other.getByText('Endlich Platz für die Dienste', { exact: false }).first().click();
|
||||
await other.keyboard.press('End');
|
||||
await other.keyboard.type(': Paperless und Immich', { delay: 20 });
|
||||
|
||||
await tab(page, 'Workspace');
|
||||
await page.getByText(/^Homelab 2\.0/).first().click();
|
||||
await page.waitForFunction(() => document.querySelectorAll('textarea').length > 5, null, { timeout: 10000 });
|
||||
await page.getByText('User', { exact: false }).first().waitFor({ timeout: 15000 }).catch(() => {});
|
||||
await sleep(2000);
|
||||
await shot(page, 'workspace_desktop.png');
|
||||
|
||||
await tab(page, 'Listen');
|
||||
await page.getByText('Wocheneinkauf', { exact: true }).first().click().catch(() => {});
|
||||
await shot(page, 'Listen_desktop.png');
|
||||
|
||||
const mobile = await open(browser, admin, MOBILE);
|
||||
await mobile.getByText('Später im Browser weiter nutzen').click({ timeout: 5000 }).catch(() => {}); // install prompt
|
||||
await shot(mobile, 'Mobile_Triage.png');
|
||||
|
||||
await browser.close();
|
||||
};
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds MindSift, starts it in demo mode with a fresh database and renews the README screenshots
|
||||
# in screenshots/. Requires podman (or docker via ENGINE=docker). Usage: e2e/screenshots.sh
|
||||
set -euo pipefail
|
||||
ENGINE="${ENGINE:-podman}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
WORK="$(mktemp -d)"
|
||||
trap '"$ENGINE" rm -f mindsift-shots >/dev/null 2>&1 || true; rm -rf "$WORK"' EXIT
|
||||
|
||||
echo "Baue Image …"
|
||||
# Version in the header, e.g. APP_VERSION=v0.2 e2e/screenshots.sh
|
||||
"$ENGINE" build -q --build-arg APP_VERSION="${APP_VERSION:-}" -t mindsift-e2e-image "$ROOT" >/dev/null
|
||||
"$ENGINE" run -d --name mindsift-shots --network host -e PORT=18081 -e MINDA_DEMO=true \
|
||||
-e JWT_SECRET=screenshots-only-secret -v "$WORK":/data:Z mindsift-e2e-image >/dev/null
|
||||
for _ in $(seq 1 60); do curl -s -m 1 http://localhost:18081/api/v1/health >/dev/null && break; sleep 0.5; done
|
||||
|
||||
echo "Erstelle Screenshots …"
|
||||
"$ENGINE" run --rm --network host -e MINDSIFT_URL=http://localhost:18081 -e SHOTS_DIR=/shots -e LANG=de_DE.UTF-8 \
|
||||
-v "$ROOT/e2e":/e2e:Z -v "$ROOT/screenshots":/shots:Z -w /e2e mcr.microsoft.com/playwright:v1.48.2-jammy \
|
||||
sh -c 'apt-get update -qq >/dev/null && apt-get install -y -qq fonts-inter >/dev/null 2>&1; npm install --no-audit --no-fund >/dev/null 2>&1 && node screenshots.mjs'
|
||||
Reference in new issue
Block a user