@tuturuuu/utils 0.27.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/biome.json +1 -1
- package/package.json +7 -7
- package/src/__tests__/app-url.test.ts +1 -0
- package/src/ai-temp-auth-constants.ts +2 -0
- package/src/ai-temp-auth.ts +2 -1
- package/src/calendar-utils.ts +10 -1
- package/src/internal-domains.ts +3 -0
- package/src/keyboard-shortcuts.ts +40 -0
- package/src/launchable-apps.ts +12 -0
- package/src/meet-creation-policy.test.ts +21 -0
- package/src/meet-creation-policy.ts +6 -0
- package/src/onboarding.ts +1 -0
- package/src/platform-release.test.ts +1 -1
- package/src/platform-release.ts +1 -1
- package/src/portless.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.28.0](https://github.com/tutur3u/platform/compare/utils-v0.27.1...utils-v0.28.0) (2026-09-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **calendar:** add installable Tasks and Calendar with bounded caching ([#5231](https://github.com/tutur3u/platform/issues/5231)) ([7078824](https://github.com/tutur3u/platform/commit/70788244874d811da3e1ce3a9af2c9389739a8c3))
|
|
9
|
+
* **colab:** launch multiplayer prompt workshops on Cloudflare ([7a4a3f9](https://github.com/tutur3u/platform/commit/7a4a3f9b4a12a0d7439cdb296398a21c0cd8939f))
|
|
10
|
+
* **meet:** host on Cloudflare and restrict meeting creation ([#5223](https://github.com/tutur3u/platform/issues/5223)) ([dff093b](https://github.com/tutur3u/platform/commit/dff093b4b6ade986ffd54f3c756d08589e47ad8e))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **calendar:** harden PWA lifecycle and cache retention ([14565c0](https://github.com/tutur3u/platform/commit/14565c0106e74b947e152fd8bb787de7f70bc54d))
|
|
16
|
+
* **calendar:** recover failed syncs and redesign calendar views ([7d22948](https://github.com/tutur3u/platform/commit/7d22948095a29d236f698d42dde362fc38311dd0))
|
|
17
|
+
* **calendar:** recover failed syncs and redesign calendar views ([#5230](https://github.com/tutur3u/platform/issues/5230)) ([e52605d](https://github.com/tutur3u/platform/commit/e52605d671f921b1564746afcdedf2478deee78e))
|
|
18
|
+
* **meet:** restrict meeting hosts and restore reliable call media ([2a8a533](https://github.com/tutur3u/platform/commit/2a8a53355181fc8d429653506f7c845c02cf7ae9))
|
|
19
|
+
|
|
3
20
|
## [0.27.1](https://github.com/tutur3u/platform/compare/utils-v0.27.0...utils-v0.27.1) (2026-09-04)
|
|
4
21
|
|
|
5
22
|
|
package/biome.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/utils",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.28.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tutur3u/platform",
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"@noble/ciphers": "^2.4.0",
|
|
21
21
|
"@noble/hashes": "^2.4.0",
|
|
22
22
|
"@tanstack/react-query": "^5.102.8",
|
|
23
|
-
"@tiptap/core": "3.31.
|
|
24
|
-
"@tiptap/react": "3.31.
|
|
23
|
+
"@tiptap/core": "3.31.2",
|
|
24
|
+
"@tiptap/react": "3.31.2",
|
|
25
25
|
"@tuturuuu/google": "0.1.0",
|
|
26
26
|
"@tuturuuu/icons": "0.1.0",
|
|
27
|
-
"@tuturuuu/internal-api": "0.
|
|
27
|
+
"@tuturuuu/internal-api": "0.34.0",
|
|
28
28
|
"@tuturuuu/supabase": "0.5.1",
|
|
29
29
|
"@upstash/ratelimit": "^2.0.8",
|
|
30
30
|
"@upstash/redis": "^1.38.3",
|
|
31
|
-
"cn": "^0.2.
|
|
31
|
+
"cn": "^0.2.5",
|
|
32
32
|
"date-fns": "^4.4.0",
|
|
33
33
|
"dayjs": "^1.11.23",
|
|
34
34
|
"diff": "^9.0.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"zod": "^4.5.4"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@tuturuuu/types": "0.
|
|
47
|
+
"@tuturuuu/types": "0.29.0",
|
|
48
48
|
"@tuturuuu/typescript-config": "0.1.2",
|
|
49
49
|
"@types/diff": "^8.0.0",
|
|
50
50
|
"@types/node": "^26.4.1",
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
"./encryption": "./src/encryption/index.ts",
|
|
77
77
|
"./finance": "./src/finance/index.ts"
|
|
78
78
|
},
|
|
79
|
-
"packageManager": "bun@1.4.
|
|
79
|
+
"packageManager": "bun@1.4.1"
|
|
80
80
|
}
|
|
@@ -292,6 +292,7 @@ describe('Portless app origin registry', () => {
|
|
|
292
292
|
'https://calendar.tuturuuu.localhost',
|
|
293
293
|
'https://chat.tuturuuu.localhost',
|
|
294
294
|
'https://cms.tuturuuu.localhost',
|
|
295
|
+
'https://colab.tuturuuu.localhost',
|
|
295
296
|
'https://contacts.tuturuuu.localhost',
|
|
296
297
|
'https://docs.tuturuuu.localhost',
|
|
297
298
|
'https://drive.tuturuuu.localhost',
|
package/src/ai-temp-auth.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { AI_TEMP_AUTH_HEADER } from './ai-temp-auth-constants';
|
|
2
3
|
import { getUpstashRestRedisClient } from './upstash-rest';
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
+
export { AI_TEMP_AUTH_HEADER } from './ai-temp-auth-constants';
|
|
5
6
|
export const AI_TEMP_AUTH_TTL_SECONDS = 60;
|
|
6
7
|
export const AI_CREDIT_SNAPSHOT_TTL_SECONDS = 15;
|
|
7
8
|
|
package/src/calendar-utils.ts
CHANGED
|
@@ -15,7 +15,16 @@ export function isAllDayEvent(
|
|
|
15
15
|
const durationMs = end.diff(start, 'millisecond');
|
|
16
16
|
const isMultipleOf24Hours = durationMs % (24 * 60 * 60 * 1000) === 0;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const spansLocalMidnights =
|
|
19
|
+
start.startOf('day').valueOf() === start.valueOf() &&
|
|
20
|
+
end.startOf('day').valueOf() === end.valueOf();
|
|
21
|
+
const wallDuration =
|
|
22
|
+
durationMs + (end.utcOffset() - start.utcOffset()) * 60_000;
|
|
23
|
+
return (
|
|
24
|
+
durationMs > 0 &&
|
|
25
|
+
(isMultipleOf24Hours ||
|
|
26
|
+
(spansLocalMidnights && wallDuration % (24 * 60 * 60 * 1000) === 0))
|
|
27
|
+
);
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
// Helper function to convert Google Calendar all-day events to proper timezone
|
package/src/internal-domains.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from './portless';
|
|
5
5
|
|
|
6
6
|
export const PRODUCTION_INTERNAL_APP_DOMAINS = [
|
|
7
|
+
{ name: 'colab', url: 'https://colab.tuturuuu.com' },
|
|
7
8
|
{
|
|
8
9
|
name: 'ai',
|
|
9
10
|
url: 'https://ai.tuturuuu.com',
|
|
@@ -131,6 +132,7 @@ export const PRODUCTION_INTERNAL_APP_DOMAINS = [
|
|
|
131
132
|
] as const;
|
|
132
133
|
|
|
133
134
|
export const PORTLESS_INTERNAL_APP_DOMAINS = [
|
|
135
|
+
{ name: 'colab', url: getTuturuuuPortlessAppOrigin('colab') },
|
|
134
136
|
{
|
|
135
137
|
name: 'ai',
|
|
136
138
|
url: getTuturuuuPortlessAppOrigin('ai'),
|
|
@@ -250,6 +252,7 @@ export const PORTLESS_INTERNAL_APP_DOMAINS = [
|
|
|
250
252
|
] as const;
|
|
251
253
|
|
|
252
254
|
export const LOCALHOST_INTERNAL_APP_DOMAINS = [
|
|
255
|
+
{ name: 'colab', url: 'http://localhost:8795' },
|
|
253
256
|
{
|
|
254
257
|
name: 'ai',
|
|
255
258
|
url: 'http://localhost:7829',
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Shared guards for document/window keyboard commands. */
|
|
2
|
+
export function isEditableShortcutTarget(target: EventTarget | null): boolean {
|
|
3
|
+
return (
|
|
4
|
+
typeof Element !== 'undefined' &&
|
|
5
|
+
target instanceof Element &&
|
|
6
|
+
Boolean(
|
|
7
|
+
target.closest(
|
|
8
|
+
'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"]'
|
|
9
|
+
)
|
|
10
|
+
)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isShortcutEventIgnored(
|
|
15
|
+
event: Pick<KeyboardEvent, 'defaultPrevented' | 'isComposing' | 'repeat'>
|
|
16
|
+
): boolean {
|
|
17
|
+
return event.defaultPrevented || event.isComposing || event.repeat;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isPageShortcutBlocked(event: KeyboardEvent): boolean {
|
|
21
|
+
if (isShortcutEventIgnored(event) || isEditableShortcutTarget(event.target))
|
|
22
|
+
return true;
|
|
23
|
+
const target = event.target;
|
|
24
|
+
if (
|
|
25
|
+
typeof Element !== 'undefined' &&
|
|
26
|
+
target instanceof Element &&
|
|
27
|
+
target.closest(
|
|
28
|
+
'[role="menu"], [role="listbox"], [role="dialog"], [role="alertdialog"]'
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
return true;
|
|
32
|
+
return (
|
|
33
|
+
typeof document !== 'undefined' &&
|
|
34
|
+
Boolean(
|
|
35
|
+
document.querySelector(
|
|
36
|
+
'[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]'
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
package/src/launchable-apps.ts
CHANGED
|
@@ -387,6 +387,18 @@ export const LAUNCHABLE_APPS = [
|
|
|
387
387
|
title: 'Track',
|
|
388
388
|
workspacePathResolver: workspaceRootPath,
|
|
389
389
|
},
|
|
390
|
+
{
|
|
391
|
+
aliases: ['Prompt engineering', 'Team workshops', 'Multiplayer learning'],
|
|
392
|
+
appRoot: 'apps/colab',
|
|
393
|
+
category: 'learning',
|
|
394
|
+
defaultPath: '/',
|
|
395
|
+
localhostOrigin: 'http://localhost:8795',
|
|
396
|
+
packageName: '@tuturuuu/colab',
|
|
397
|
+
portlessApp: 'colab',
|
|
398
|
+
productionUrl: 'https://colab.tuturuuu.com',
|
|
399
|
+
slug: 'colab',
|
|
400
|
+
title: 'Colab',
|
|
401
|
+
},
|
|
390
402
|
] as const satisfies readonly LaunchableApp[];
|
|
391
403
|
|
|
392
404
|
export type LaunchableAppSlug = (typeof LAUNCHABLE_APPS)[number]['slug'];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { canCreateOnlineMeeting } from './meet-creation-policy';
|
|
3
|
+
|
|
4
|
+
describe('online meeting creator domain', () => {
|
|
5
|
+
it.each(['host@tuturuuu.com', 'Host@TUTURUUU.COM'])('allows %s', (email) => {
|
|
6
|
+
expect(canCreateOnlineMeeting(email)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it.each([
|
|
9
|
+
null,
|
|
10
|
+
undefined,
|
|
11
|
+
'',
|
|
12
|
+
'@tuturuuu.com',
|
|
13
|
+
'host@gmail.com',
|
|
14
|
+
'host@sub.tuturuuu.com',
|
|
15
|
+
'host@tuturuuu.com.evil.test',
|
|
16
|
+
'host@evil.test@tuturuuu.com',
|
|
17
|
+
' host@tuturuuu.com',
|
|
18
|
+
])('rejects %s', (email) => {
|
|
19
|
+
expect(canCreateOnlineMeeting(email)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
package/src/onboarding.ts
CHANGED
|
@@ -71,6 +71,7 @@ export const GOAL_RECOMMENDATIONS = {
|
|
|
71
71
|
} as const satisfies Record<OnboardingGoal, readonly string[]>;
|
|
72
72
|
|
|
73
73
|
const MISSIONS = [
|
|
74
|
+
['colab', 'learn', 'reversible', ['prompt', 'simulate', 'review']],
|
|
74
75
|
['platform', 'focus', 'reversible', ['overview', 'apps', 'next_move']],
|
|
75
76
|
['calendar', 'focus', 'reversible', ['schedule', 'timebox', 'review']],
|
|
76
77
|
[
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe('platform release metadata', () => {
|
|
9
9
|
it('uses the centralized shared browser app version', () => {
|
|
10
|
-
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.
|
|
10
|
+
expect(TUTURUUU_PLATFORM_VERSION).toBe('0.46.0'); // x-release-please-version
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it('preserves the source timestamp in builtAt and derives a short hash', () => {
|
package/src/platform-release.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
|
|
2
2
|
|
|
3
|
-
export const TUTURUUU_PLATFORM_VERSION = '0.
|
|
3
|
+
export const TUTURUUU_PLATFORM_VERSION = '0.46.0'; // x-release-please-version
|
|
4
4
|
|
|
5
5
|
export type PlatformBuildMetadataInput = {
|
|
6
6
|
/** ISO timestamp of the source commit used for the build, not deployment time. */
|
package/src/portless.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const TUTURUUU_PORTLESS_APP_ORIGINS = {
|
|
|
11
11
|
calendar: `https://calendar.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
12
12
|
chat: `https://chat.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
13
13
|
cms: `https://cms.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
14
|
+
colab: `https://colab.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
14
15
|
contacts: `https://contacts.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
15
16
|
docs: `https://docs.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|
|
16
17
|
drive: `https://drive.${TUTURUUU_PORTLESS_ROOT_HOST}`,
|