@tuturuuu/utils 0.25.4 → 0.26.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.26.0](https://github.com/tutur3u/platform/compare/utils-v0.25.5...utils-v0.26.0) (2026-08-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * **forms:** add landing page, per-form SEO, realtime collaboration, and embeds ([#5145](https://github.com/tutur3u/platform/issues/5145)) ([6896515](https://github.com/tutur3u/platform/commit/6896515b099b28bbebc873f98733879bf108ef89))
9
+ * **forms:** add six embed modes and a public embed SDK ([5d0b68b](https://github.com/tutur3u/platform/commit/5d0b68b3ca5ce3d4cd6cd119e2b0ad79b014aedd))
10
+ * **tu-do:** search board tasks by ticket identifier ([e81891c](https://github.com/tutur3u/platform/commit/e81891c7424b530322e3250d917636a988e278fe))
11
+ * **tu-do:** search board tasks by ticket identifier ([#5142](https://github.com/tutur3u/platform/issues/5142)) ([18c0e3e](https://github.com/tutur3u/platform/commit/18c0e3ea6a20522959a0b66ed8dfd5a5699daec4))
12
+
13
+ ## [0.25.5](https://github.com/tutur3u/platform/compare/utils-v0.25.4...utils-v0.25.5) (2026-08-25)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **contacts:** proxy feedbacks before dynamic users ([910b79e](https://github.com/tutur3u/platform/commit/910b79ef5a53e31b865cb1290e2f2b29d19c1702))
19
+
3
20
  ## [0.25.4](https://github.com/tutur3u/platform/compare/utils-v0.25.3...utils-v0.25.4) (2026-08-21)
4
21
 
5
22
 
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "root": false,
3
- "$schema": "https://biomejs.dev/schemas/2.5.9/schema.json",
3
+ "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
4
4
  "extends": "//"
5
5
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tuturuuu/utils",
3
3
  "license": "MIT",
4
- "version": "0.25.4",
4
+ "version": "0.26.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tutur3u/platform",
@@ -19,12 +19,12 @@
19
19
  "@dnd-kit/core": "^6.3.1",
20
20
  "@noble/ciphers": "^2.3.0",
21
21
  "@noble/hashes": "^2.3.0",
22
- "@tanstack/react-query": "^5.101.4",
22
+ "@tanstack/react-query": "^5.102.1",
23
23
  "@tiptap/core": "3.30.2",
24
24
  "@tiptap/react": "3.30.2",
25
25
  "@tuturuuu/google": "0.1.0",
26
26
  "@tuturuuu/icons": "0.1.0",
27
- "@tuturuuu/internal-api": "0.31.1",
27
+ "@tuturuuu/internal-api": "0.33.0",
28
28
  "@tuturuuu/supabase": "0.5.1",
29
29
  "@upstash/ratelimit": "^2.0.8",
30
30
  "@upstash/redis": "^1.38.2",
@@ -45,7 +45,7 @@
45
45
  "zod": "^4.4.3"
46
46
  },
47
47
  "devDependencies": {
48
- "@tuturuuu/types": "0.27.0",
48
+ "@tuturuuu/types": "0.28.0",
49
49
  "@tuturuuu/typescript-config": "0.1.2",
50
50
  "@types/diff": "^8.0.0",
51
51
  "@types/node": "^26.2.0",
@@ -326,7 +326,7 @@ describe('application metadata coverage', () => {
326
326
  )
327
327
  .map((entry) => resolve(entry.parentPath, entry.name));
328
328
 
329
- expect(marketingLayouts).toHaveLength(37);
329
+ expect(marketingLayouts).toHaveLength(38);
330
330
  for (const layoutPath of marketingLayouts) {
331
331
  expect(readFileSync(layoutPath, 'utf8')).toMatch(/pathname:\s*['`]/);
332
332
  }
@@ -290,6 +290,11 @@ describe('createTuturuuuWebWorkspaceApiRewrites', () => {
290
290
  destination:
291
291
  'https://tuturuuu.com/api/v1/workspaces/:wsId/settings/permissions',
292
292
  },
293
+ {
294
+ source: '/api/v1/workspaces/:wsId/users/feedbacks',
295
+ destination:
296
+ 'https://tuturuuu.com/api/v1/workspaces/:wsId/users/feedbacks',
297
+ },
293
298
  ]);
294
299
  });
295
300
  });
@@ -495,4 +500,35 @@ describe('trimTrailingSlashes', () => {
495
500
  'https://example.com/path'
496
501
  );
497
502
  });
503
+
504
+ it('excludes opted-in paths from the anti-framing rule', async () => {
505
+ const config = createTuturuuuNextConfig({
506
+ framablePathPatterns: ['embed/[^/]+'],
507
+ });
508
+
509
+ const headers = await config.headers?.();
510
+ const source = headers?.[0]?.source ?? '';
511
+
512
+ expect(source).toContain('embed/[^/]+(?:/|$)');
513
+ // The platform default must survive an app adding its own patterns.
514
+ expect(source).toContain('external-projects/assets/[^/]+/webgl(?:/|$)');
515
+
516
+ // The `source` is a path-to-regexp pattern, so assert the lookahead does
517
+ // what it claims: opted-in paths must not match the deny rule, and
518
+ // everything else still must.
519
+ const lookahead = new RegExp(`^${source.slice('/:path('.length, -1)}$`);
520
+ expect(lookahead.test('embed/abc123')).toBe(false);
521
+ expect(lookahead.test('embed/abc123/preview')).toBe(false);
522
+ expect(lookahead.test('embedded-elsewhere')).toBe(true);
523
+ expect(lookahead.test('f/abc123')).toBe(true);
524
+ expect(lookahead.test('personal/forms')).toBe(true);
525
+ });
526
+
527
+ it('does not leak framablePathPatterns into the Next config object', () => {
528
+ const config = createTuturuuuNextConfig({
529
+ framablePathPatterns: ['embed/[^/]+'],
530
+ });
531
+
532
+ expect(config).not.toHaveProperty('framablePathPatterns');
533
+ });
498
534
  });
@@ -16,6 +16,7 @@ export const TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS = [
16
16
  export const TUTURUUU_WEB_WORKSPACE_API_RESERVED_PATHS = [
17
17
  '/api/workspaces/invitations',
18
18
  '/api/v1/workspaces/:wsId/settings/permissions',
19
+ '/api/v1/workspaces/:wsId/users/feedbacks',
19
20
  ] as const;
20
21
 
21
22
  type NextImageConfig = NonNullable<NextConfig['images']>;
@@ -40,8 +41,31 @@ export const TUTURUUU_NEXT_IMAGE_REMOTE_PATTERNS = [
40
41
 
41
42
  export const TUTURUUU_NEXT_IMAGE_MINIMUM_CACHE_TTL_SECONDS = 7 * 24 * 60 * 60;
42
43
 
43
- const TUTURUUU_ANTI_FRAMING_SOURCE =
44
- '/:path((?!api/v1/workspaces/[^/]+/external-projects/assets/[^/]+/webgl(?:/|$)).*)';
44
+ /**
45
+ * Paths every app excludes from the anti-framing headers.
46
+ *
47
+ * Framing is denied by default across the platform; anything listed here has
48
+ * been deliberately designed to be embedded in a third-party page.
49
+ */
50
+ const TUTURUUU_DEFAULT_FRAMABLE_PATTERNS = [
51
+ 'api/v1/workspaces/[^/]+/external-projects/assets/[^/]+/webgl',
52
+ ];
53
+
54
+ /**
55
+ * Builds the anti-framing `source` as a negative lookahead over every framable
56
+ * path, so those paths simply never match the deny rule.
57
+ *
58
+ * A later permissive header would not reliably win: `X-Frame-Options` has no
59
+ * "allow from any origin" value, so the deny header has to be absent rather
60
+ * than overridden.
61
+ */
62
+ function buildAntiFramingSource(framablePathPatterns: readonly string[]) {
63
+ const alternatives = framablePathPatterns
64
+ .map((pattern) => `${pattern}(?:/|$)`)
65
+ .join('|');
66
+
67
+ return `/:path((?!${alternatives}).*)`;
68
+ }
45
69
 
46
70
  const TUTURUUU_ANTI_FRAMING_HEADERS = [
47
71
  {
@@ -110,14 +134,32 @@ export function getTuturuuuNextOptimizePackageImports(
110
134
  : mergeStringArrays(TUTURUUU_NEXT_OPTIMIZE_PACKAGE_IMPORTS, appImports);
111
135
  }
112
136
 
113
- export function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
137
+ export interface TuturuuuNextConfigOptions extends NextConfig {
138
+ /**
139
+ * Path patterns (no leading slash, regex fragments) that this app allows to
140
+ * be framed by third-party sites. Excluded from the platform-wide
141
+ * `frame-ancestors 'none'` / `X-Frame-Options: DENY` rule.
142
+ *
143
+ * Opt in per route, never per app: everything not listed here stays denied.
144
+ */
145
+ framablePathPatterns?: readonly string[];
146
+ }
147
+
148
+ export function createTuturuuuNextConfig(
149
+ config: TuturuuuNextConfigOptions = {}
150
+ ): NextConfig {
114
151
  const experimentalConfig = config.experimental ?? {};
115
152
  const imageConfig = config.images ?? {};
153
+ const { framablePathPatterns, ...nextConfig } = config;
154
+ const antiFramingSource = buildAntiFramingSource([
155
+ ...TUTURUUU_DEFAULT_FRAMABLE_PATTERNS,
156
+ ...(framablePathPatterns ?? []),
157
+ ]);
116
158
 
117
159
  return {
118
160
  reactStrictMode: true,
119
161
  poweredByHeader: false,
120
- ...config,
162
+ ...nextConfig,
121
163
  reactCompiler: isTuturuuuNextReactCompilerEnabled(),
122
164
  cacheComponents:
123
165
  config.cacheComponents ?? isTuturuuuNextCacheComponentsEnabled(),
@@ -158,10 +200,10 @@ export function createTuturuuuNextConfig(config: NextConfig = {}): NextConfig {
158
200
  async headers() {
159
201
  return [
160
202
  {
161
- source: TUTURUUU_ANTI_FRAMING_SOURCE,
203
+ source: antiFramingSource,
162
204
  headers: TUTURUUU_ANTI_FRAMING_HEADERS,
163
205
  },
164
- ...((await config.headers?.()) ?? []),
206
+ ...((await nextConfig.headers?.()) ?? []),
165
207
  ];
166
208
  },
167
209
  };
@@ -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.40.2'); // x-release-please-version
10
+ expect(TUTURUUU_PLATFORM_VERSION).toBe('0.42.0'); // x-release-please-version
11
11
  });
12
12
 
13
13
  it('preserves the source timestamp in builtAt and derives a short hash', () => {
@@ -1,6 +1,6 @@
1
1
  import { PLATFORM_BUILD_METADATA } from './generated/platform-build-metadata';
2
2
 
3
- export const TUTURUUU_PLATFORM_VERSION = '0.40.2'; // x-release-please-version
3
+ export const TUTURUUU_PLATFORM_VERSION = '0.42.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. */
@@ -6,7 +6,12 @@ export * from './task-helper/optimistic-task-cache';
6
6
  export * from './task-helper/personal-external-staging';
7
7
  export * from './task-helper/recycle-bin';
8
8
  export * from './task-helper/relationships';
9
- export { getTicketIdentifier } from './task-helper/shared';
9
+ export {
10
+ getTaskIdentifierForSearch,
11
+ getTicketIdentifier,
12
+ isTicketIdentifierLikeQuery,
13
+ normalizeTaskSearchValue,
14
+ } from './task-helper/shared';
10
15
  export * from './task-helper/sort-keys';
11
16
  export * from './task-helper/task-hooks-basic';
12
17
  export * from './task-helper/task-hooks-move';