Compare commits

..
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 72277399f0 feat(about): show the desktop version and the web build's commit and date (#276)
CI / Build & Quality Checks (pull_request) Successful in 1m46s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m27s
Settings → About only showed cinny's package version, the same on web
and desktop and unchanged across many deploys a day. vite.config.js now
stamps the short commit (git, or LOTUS_BUILD_COMMIT; "unknown" without
git) and build time into the bundle, and About shows "Web build <commit>
· built <date>", linking the commit. On desktop it also shows "Desktop
<version>" from Tauri's built-in plugin:app|version (allowed by
core:default).

Fixes #276

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 15:24:37 -04:00
6 changed files with 74 additions and 50 deletions
+4 -22
View File
@@ -20,12 +20,7 @@ import { IContent } from 'matrix-js-sdk';
import { useTimestampFormatter } from '../../hooks/useTimestampFormatter';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { stopPropagation } from '../../utils/keyboard';
import {
describeScheduleError,
MAX_SCHEDULE_AHEAD_MS,
scheduleMessage,
SCHEDULE_TOO_FAR_TEXT,
} from '../../utils/scheduledMessages';
import { scheduleMessage } from '../../utils/scheduledMessages';
import { useModalStyle } from '../../hooks/useModalStyle';
import {
toLocalDate,
@@ -126,13 +121,7 @@ export function ScheduleMessageModal({
return;
}
const diffMs = sendAt.getTime() - Date.now();
// [Gitea #275] Say so as soon as a too-late time is picked, not on submit.
const tooFar = diffMs > MAX_SCHEDULE_AHEAD_MS;
setError((e) => {
if (tooFar) return SCHEDULE_TOO_FAR_TEXT;
return e === SCHEDULE_TOO_FAR_TEXT ? null : e;
});
if (diffMs < 60_000 || tooFar) {
if (diffMs < 60_000) {
setPreview(null);
return;
}
@@ -160,10 +149,6 @@ export function ScheduleMessageModal({
setError('Scheduled time must be at least 1 minute in the future.');
return;
}
if (diffMs > MAX_SCHEDULE_AHEAD_MS) {
setError(SCHEDULE_TOO_FAR_TEXT);
return;
}
if (!messageText.trim()) {
setError('Please enter a message to schedule.');
return;
@@ -177,7 +162,7 @@ export function ScheduleMessageModal({
onScheduled(delayId, sendAt.getTime(), content);
onClose();
} catch (err) {
setError(describeScheduleError(err));
setError(err instanceof Error ? err.message : 'Failed to schedule message.');
setSubmitting(false);
}
};
@@ -262,8 +247,6 @@ export function ScheduleMessageModal({
<input
id="schedule-date"
type="date"
min={toLocalDate(new Date())}
max={toLocalDate(new Date(Date.now() + MAX_SCHEDULE_AHEAD_MS))}
value={dateValue}
onChange={(e) => setDateValue(e.target.value)}
style={pickerInputStyle(color, config)}
@@ -311,8 +294,7 @@ export function ScheduleMessageModal({
</Box>
</Box>
) : (
(dateValue || timeValue) &&
error !== SCHEDULE_TOO_FAR_TEXT && (
(dateValue || timeValue) && (
<Text size="T200" style={{ color: color.Critical.Main }}>
Must be at least 1 minute in the future
</Text>
+47
View File
@@ -8,6 +8,7 @@ import { getOriginBaseUrl, withOriginBaseUrl } from '../../../pages/pathUtils';
import pkg from '../../../../../package.json';
import { clearCacheAndReload } from '../../../../client/initMatrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { tauriInvoke } from '../../../hooks/useTauri';
const LotusLogo = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotus-logo.png');
@@ -78,6 +79,51 @@ function useServerSupport(): { support: MSC1929Support | null; loading: boolean
type AboutProps = {
requestClose: () => void;
};
/**
* [Gitea #276] Which build someone is on: the web deploy's commit and build
* date (stamped in by vite.config.js), plus the desktop app's own version from
* Tauri, the number a desktop or updater bug report needs.
*/
function BuildInfo() {
const [desktopVersion, setDesktopVersion] = useState<string>();
useEffect(() => {
tauriInvoke()?.('plugin:app|version')
.then((v) => {
if (typeof v === 'string') setDesktopVersion(v);
})
.catch(() => undefined);
}, []);
const commit = __LOTUS_BUILD_COMMIT__;
const built = new Date(__LOTUS_BUILD_DATE__);
const builtText = Number.isNaN(built.getTime())
? undefined
: built.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
return (
<Box direction="Column" gap="0">
{desktopVersion && (
<Text size="T200" priority="300">
Desktop {desktopVersion}
</Text>
)}
<Text size="T200" priority="300">
Web build{' '}
{commit !== 'unknown' ? (
<a
href={`https://code.lotusguild.org/LotusGuild/cinny/commit/${commit}`}
target="_blank"
rel="noreferrer noopener"
>
{commit}
</a>
) : (
commit
)}
{builtText && ` · built ${builtText}`}
</Text>
</Box>
);
}
export function About({ requestClose }: AboutProps) {
const mx = useMatrixClient();
const { support: serverSupport, loading: supportLoading } = useServerSupport();
@@ -117,6 +163,7 @@ export function About({ requestClose }: AboutProps) {
<Text size="T200">v{pkg.version}</Text>
</Box>
<Text>A Matrix client for Lotus Guild.</Text>
<BuildInfo />
</Box>
<Box gap="200" wrap="Wrap">
-12
View File
@@ -179,15 +179,3 @@ test('groupScheduledByRoom: groups and sorts by sendAt', () => {
);
assert.equal(g.get('!2')?.length, 1);
});
test('[#275] the server limit maps to friendly text; other errors keep theirs', async () => {
const { describeScheduleError, SCHEDULE_TOO_FAR_TEXT, MAX_SCHEDULE_AHEAD_MS } =
await import('./scheduledMessages');
assert.equal(MAX_SCHEDULE_AHEAD_MS, 86_400_000);
const tooLarge = Object.assign(new Error('The requested delay (172800000ms) exceeds…'), {
errcode: 'ORG.MATRIX.MSC4140_DELAY_TOO_LARGE',
});
assert.equal(describeScheduleError(tooLarge), SCHEDULE_TOO_FAR_TEXT);
assert.equal(describeScheduleError(new Error('Boom')), 'Boom');
assert.equal(describeScheduleError('nope'), 'Failed to schedule message.');
});
-16
View File
@@ -1,22 +1,6 @@
import { IContent, MatrixClient, Method } from 'matrix-js-sdk';
import type { ScheduledMessage } from '../state/scheduledMessages';
/**
* [Gitea #275] Production Synapse caps delayed events at
* `max_event_delay_duration: 24h`; a later time is rejected with
* ORG.MATRIX.MSC4140_DELAY_TOO_LARGE. The dialog enforces the same limit and
* the server's error maps to the same text, in case the limit changes.
*/
export const MAX_SCHEDULE_AHEAD_MS = 24 * 60 * 60 * 1000;
export const SCHEDULE_TOO_FAR_TEXT = 'You can schedule up to 24 hours ahead.';
/** A user-facing sentence for a failed schedule. */
export const describeScheduleError = (err: unknown): string => {
const errcode = (err as { errcode?: unknown } | null)?.errcode;
if (errcode === 'ORG.MATRIX.MSC4140_DELAY_TOO_LARGE') return SCHEDULE_TOO_FAR_TEXT;
return err instanceof Error ? err.message : 'Failed to schedule message.';
};
/**
* Schedule a message via MSC4140 (delayed messages).
* Synapse implementation: pass org.matrix.msc4140.delay on the standard
+4
View File
@@ -1,5 +1,9 @@
/// <reference types="vite/client" />
// [Gitea #276] Set by vite.config.js `define`.
declare const __LOTUS_BUILD_COMMIT__: string;
declare const __LOTUS_BUILD_DATE__: string;
declare module 'browser-encrypt-attachment' {
export interface EncryptedAttachmentInfo {
v: string;
+19
View File
@@ -6,6 +6,7 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import { VitePWA } from 'vite-plugin-pwa';
import fs from 'fs';
import { execSync } from 'child_process';
import path from 'path';
import buildConfig from './build.config';
@@ -272,8 +273,26 @@ const vendorChunks = (id) => {
if (id.includes('node_modules/chroma-js')) return 'chroma';
};
// [Gitea #276] Which build is this? The short commit and build time are shown
// in Settings → About (web deploys change many times a day while the package
// version stays the same). LOTUS_BUILD_COMMIT overrides; no git → "unknown".
const buildCommit = (() => {
if (process.env.LOTUS_BUILD_COMMIT) return process.env.LOTUS_BUILD_COMMIT.slice(0, 12);
try {
return execSync('git rev-parse --short=8 HEAD', { stdio: ['ignore', 'pipe', 'ignore'] })
.toString()
.trim();
} catch {
return 'unknown';
}
})();
export default defineConfig({
appType: 'spa',
define: {
__LOTUS_BUILD_COMMIT__: JSON.stringify(buildCommit),
__LOTUS_BUILD_DATE__: JSON.stringify(new Date().toISOString()),
},
publicDir: false,
base: buildConfig.base,
server: {