@taskless-app/shared 0.1.41 → 0.1.43

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.
@@ -0,0 +1,8 @@
1
+ /** Адрес в конце строки — любой (для починки href уже собранной ссылки). */
2
+ export declare function isUrlTail(text: string): boolean;
3
+ /** Адрес в конце строки, который выглядит ОБОРВАННЫМ, а не просто законченным. */
4
+ export declare function isBrokenUrlTail(text: string): boolean;
5
+ /** Строка целиком — хвост оборванного адреса, а не новая мысль. */
6
+ export declare function isUrlContinuation(text: string): boolean;
7
+ /** Плоский текст: строки-хвосты приклеиваются к предыдущей строке без пробела. */
8
+ export declare function joinBrokenUrlLines(text: string): string;
@@ -0,0 +1,111 @@
1
+ // Склейка адреса, разорванного переносами строк.
2
+ //
3
+ // PRJ-122: длинный адрес, скопированный из PDF/письма/мессенджера, приезжает
4
+ // разбитым переносами по `&`, и разбор «строка = абзац» линкует только первую
5
+ // строку — хвост параметров остаётся обычным текстом.
6
+ //
7
+ // MOVE-68: то же самое приезжает и ИЗ ИМПОРТА — так адрес лежал в markdown
8
+ // YouTrack. Поэтому чистая текстовая часть склейки живёт здесь, в shared: DOM
9
+ // ей не нужен, а потребителей у неё двое — редактор (путь вставки,
10
+ // `packages/editor/src/markdownPaste.ts`, по всему тексту буфера) и коннекторы
11
+ // импорта (`youtrack/body.ts`, поабзацно: пока строки абзаца ещё строки, а не
12
+ // hardBreak'ы, из которых линкуется только первая).
13
+ //
14
+ // Условие склейки нарочно узкое, чтобы не портить обычный многострочный текст:
15
+ // 1) предыдущая строка кончается ОБОРВАННЫМ адресом: в нём уже начался query
16
+ // (`?…`) либо он обрывается на разделителе (`?`, `&`, `=`, `/`);
17
+ // 2) следующая целиком — хвост этого адреса: либо параметр query
18
+ // (`&key=value`, `?key=value`), либо продолжение пути/фрагмента
19
+ // (`/…`, `#…`, `=…`) — и в любом случае БЕЗ пробелов внутри.
20
+ // Требование (2) отсекает «Документация: https://x\n/api/tasks — список задач»:
21
+ // строка с пробелами хвостом адреса не считается.
22
+ // Требование (1) — правка по ревью PR #1130: «просто адрес» признаком обрыва не
23
+ // считается, иначе частый паттерн
24
+ // Fixed in https://github.com/org/repo/commit/abcd123
25
+ // #456
26
+ // молча склеивался бы в один абзац, приклеивая ссылку на задачу к URL как
27
+ // fragment. По той же причине голый `#123` (номер задачи/issue) хвостом не
28
+ // считается никогда — даже после оборванного query.
29
+ const RE_URL_TAIL = /https?:\/\/\S*$/i;
30
+ const RE_URL_BROKEN_TAIL = /https?:\/\/\S*(?:\?\S*|[?&=/])$/i;
31
+ const RE_CONT_QUERY = /^[&?][^\s&?#=]+=\S*$/;
32
+ const RE_CONT_PATH = /^[/#=]\S*$/;
33
+ const RE_ISSUE_REF = /^#\d+$/;
34
+ // Огороженный код: внутри забора перевод строки ЗНАЧИМ, склейка там ломает
35
+ // содержимое. `PUBLIC_URL=https://app.example.com/` + `#PUBLIC_URL=…` — две
36
+ // строки .env, а не разорванный адрес. DOM-близнец этой склейки код пропускает
37
+ // давно (`joinBrokenUrlHtml` → `significantTexts`: `closest("pre, code")`),
38
+ // текстовая версия должна вести себя так же.
39
+ //
40
+ // Грамматика забора — CommonMark, ровно как у `marked`, которым разбирает
41
+ // вставку редактор (единственный, кто зовёт склейку по ЦЕЛОМУ тексту; импорт
42
+ // YouTrack зовёт её поабзацно, когда заборы уже разобраны). Отсюда две
43
+ // строгости, без которых состояние забора расходится с парсером и склейка либо
44
+ // молча выключается до конца текста, либо, наоборот, лезет в код:
45
+ // • открывающий забор может нести info string (```` ```js ````, ```` ``` смотри лог ````),
46
+ // закрывающий — нет: он ГОЛЫЙ;
47
+ // • закрывать умеет только тот же символ и не короче открывшего: `~~~`
48
+ // внутри ```` ``` ````-блока — просто строка кода.
49
+ // Ревью PR #1135: широкий `/^\s*(```|~~~)/` на обе роли путал и то и другое.
50
+ const RE_FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
51
+ /** Забор закрыт: тот же символ, не короче открывшего, и после него пусто. */
52
+ function closesFence(line, marker) {
53
+ const m = RE_FENCE_OPEN.exec(line);
54
+ return m !== null && m[1][0] === marker[0] && m[1].length >= marker.length && m[2].trim() === "";
55
+ }
56
+ /** Адрес в конце строки — любой (для починки href уже собранной ссылки). */
57
+ export function isUrlTail(text) {
58
+ return RE_URL_TAIL.test(text.replace(/\s+$/, ""));
59
+ }
60
+ /** Адрес в конце строки, который выглядит ОБОРВАННЫМ, а не просто законченным. */
61
+ export function isBrokenUrlTail(text) {
62
+ return RE_URL_BROKEN_TAIL.test(text.replace(/\s+$/, ""));
63
+ }
64
+ /** Строка целиком — хвост оборванного адреса, а не новая мысль. */
65
+ export function isUrlContinuation(text) {
66
+ const t = text.trim();
67
+ if (t.length < 2 || RE_ISSUE_REF.test(t))
68
+ return false;
69
+ return RE_CONT_QUERY.test(t) || RE_CONT_PATH.test(t);
70
+ }
71
+ /** Плоский текст: строки-хвосты приклеиваются к предыдущей строке без пробела. */
72
+ export function joinBrokenUrlLines(text) {
73
+ if (!text || !/[\r\n]/.test(text))
74
+ return text;
75
+ const out = [];
76
+ let joined = false;
77
+ // Маркер открытого забора (``` / ~~~~ и т.п.) либо null — забор закрыт.
78
+ // Непарный забор оставляет режим включённым до конца текста, то есть склейки
79
+ // не будет: отказаться от склейки безопаснее, чем испортить код. Так же
80
+ // читает незакрытый забор и сам CommonMark — до конца документа.
81
+ let fence = null;
82
+ for (const line of text.split(/\r\n?|\n/)) {
83
+ if (fence !== null) {
84
+ if (closesFence(line, fence))
85
+ fence = null;
86
+ out.push(line);
87
+ continue;
88
+ }
89
+ const open = RE_FENCE_OPEN.exec(line);
90
+ // Бэктик-забор с бэктиком в info string CommonMark забором не считает
91
+ // (иначе инлайн-код спутался бы с блочным) — и мы тоже.
92
+ if (open && !(open[1][0] === "`" && open[2].includes("`"))) {
93
+ fence = open[1];
94
+ out.push(line);
95
+ continue;
96
+ }
97
+ const prev = out.length > 0 ? out[out.length - 1] : undefined;
98
+ if (prev !== undefined && isBrokenUrlTail(prev) && isUrlContinuation(line)) {
99
+ out[out.length - 1] = prev.replace(/\s+$/, "") + line.trim();
100
+ joined = true;
101
+ continue;
102
+ }
103
+ out.push(line);
104
+ }
105
+ // Ничего не склеили — отдаём ИСХОДНУЮ строку, а не пересобранную. Иначе
106
+ // CRLF (буфер Windows) схлопнулся бы в \n, сравнение `text === raw` в
107
+ // clipboardTextParser редактора стало бы ложным, и любая обычная многострочная
108
+ // вставка пошла бы мимо дефолтного разбора ProseMirror — теряя марки каретки.
109
+ return joined ? out.join("\n") : text;
110
+ }
111
+ //# sourceMappingURL=brokenUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokenUrl.js","sourceRoot":"","sources":["../src/brokenUrl.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -24,3 +24,5 @@ export * from "./repoProfile.js";
24
24
  export * from "./importedIdentity.js";
25
25
  export * from "./importPackage.js";
26
26
  export * from "./descriptionText.js";
27
+ export * from "./brokenUrl.js";
28
+ export * from "./sidebarSections.js";
package/dist/index.js CHANGED
@@ -24,4 +24,6 @@ export * from "./repoProfile.js";
24
24
  export * from "./importedIdentity.js";
25
25
  export * from "./importPackage.js";
26
26
  export * from "./descriptionText.js";
27
+ export * from "./brokenUrl.js";
28
+ export * from "./sidebarSections.js";
27
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,153 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Ключи иконок секции. Значение — ключ, а не имя компонента: набор иконок в
4
+ * вебе может смениться, а сохранённые в БД профили — нет.
5
+ */
6
+ export declare const SECTION_ICONS: readonly ["folder", "star", "heart", "flag", "bookmark", "tag", "users", "briefcase", "rocket", "target", "zap", "bug", "code", "lightbulb", "bell", "coffee", "globe", "home", "inbox", "lock", "megaphone", "package", "shield", "sparkles"];
7
+ export type SectionIcon = (typeof SECTION_ICONS)[number];
8
+ export declare const sectionIconSchema: z.ZodEnum<["folder", "star", "heart", "flag", "bookmark", "tag", "users", "briefcase", "rocket", "target", "zap", "bug", "code", "lightbulb", "bell", "coffee", "globe", "home", "inbox", "lock", "megaphone", "package", "shield", "sparkles"]>;
9
+ /** Элемент секции: канал или человек. Порядок в массиве — порядок показа. */
10
+ export declare const sidebarSectionItemSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
11
+ kind: z.ZodLiteral<"room">;
12
+ id: z.ZodString;
13
+ }, "strip", z.ZodTypeAny, {
14
+ kind: "room";
15
+ id: string;
16
+ }, {
17
+ kind: "room";
18
+ id: string;
19
+ }>, z.ZodObject<{
20
+ kind: z.ZodLiteral<"user">;
21
+ id: z.ZodString;
22
+ }, "strip", z.ZodTypeAny, {
23
+ kind: "user";
24
+ id: string;
25
+ }, {
26
+ kind: "user";
27
+ id: string;
28
+ }>]>;
29
+ export declare const MAX_SECTIONS = 20;
30
+ export declare const MAX_ITEMS_PER_SECTION = 100;
31
+ export declare const sidebarSectionSchema: z.ZodObject<{
32
+ id: z.ZodString;
33
+ name: z.ZodString;
34
+ icon: z.ZodNullable<z.ZodEnum<["folder", "star", "heart", "flag", "bookmark", "tag", "users", "briefcase", "rocket", "target", "zap", "bug", "code", "lightbulb", "bell", "coffee", "globe", "home", "inbox", "lock", "megaphone", "package", "shield", "sparkles"]>>;
35
+ items: z.ZodArray<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
36
+ kind: z.ZodLiteral<"room">;
37
+ id: z.ZodString;
38
+ }, "strip", z.ZodTypeAny, {
39
+ kind: "room";
40
+ id: string;
41
+ }, {
42
+ kind: "room";
43
+ id: string;
44
+ }>, z.ZodObject<{
45
+ kind: z.ZodLiteral<"user">;
46
+ id: z.ZodString;
47
+ }, "strip", z.ZodTypeAny, {
48
+ kind: "user";
49
+ id: string;
50
+ }, {
51
+ kind: "user";
52
+ id: string;
53
+ }>]>, "many">;
54
+ }, "strip", z.ZodTypeAny, {
55
+ id: string;
56
+ name: string;
57
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
58
+ items: ({
59
+ kind: "room";
60
+ id: string;
61
+ } | {
62
+ kind: "user";
63
+ id: string;
64
+ })[];
65
+ }, {
66
+ id: string;
67
+ name: string;
68
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
69
+ items: ({
70
+ kind: "room";
71
+ id: string;
72
+ } | {
73
+ kind: "user";
74
+ id: string;
75
+ })[];
76
+ }>;
77
+ /**
78
+ * Весь набор секций. Уникальность проверяется здесь, а не на роуте: элемент
79
+ * живёт РОВНО в одном месте — иначе один канал показывается в двух секциях и
80
+ * из общего списка исчезает дважды. Поэтому пара kind+id уникальна СРАЗУ ПО
81
+ * ВСЕМ секциям, а не внутри одной.
82
+ */
83
+ export declare const sidebarSectionsSchema: z.ZodEffects<z.ZodArray<z.ZodObject<{
84
+ id: z.ZodString;
85
+ name: z.ZodString;
86
+ icon: z.ZodNullable<z.ZodEnum<["folder", "star", "heart", "flag", "bookmark", "tag", "users", "briefcase", "rocket", "target", "zap", "bug", "code", "lightbulb", "bell", "coffee", "globe", "home", "inbox", "lock", "megaphone", "package", "shield", "sparkles"]>>;
87
+ items: z.ZodArray<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
88
+ kind: z.ZodLiteral<"room">;
89
+ id: z.ZodString;
90
+ }, "strip", z.ZodTypeAny, {
91
+ kind: "room";
92
+ id: string;
93
+ }, {
94
+ kind: "room";
95
+ id: string;
96
+ }>, z.ZodObject<{
97
+ kind: z.ZodLiteral<"user">;
98
+ id: z.ZodString;
99
+ }, "strip", z.ZodTypeAny, {
100
+ kind: "user";
101
+ id: string;
102
+ }, {
103
+ kind: "user";
104
+ id: string;
105
+ }>]>, "many">;
106
+ }, "strip", z.ZodTypeAny, {
107
+ id: string;
108
+ name: string;
109
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
110
+ items: ({
111
+ kind: "room";
112
+ id: string;
113
+ } | {
114
+ kind: "user";
115
+ id: string;
116
+ })[];
117
+ }, {
118
+ id: string;
119
+ name: string;
120
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
121
+ items: ({
122
+ kind: "room";
123
+ id: string;
124
+ } | {
125
+ kind: "user";
126
+ id: string;
127
+ })[];
128
+ }>, "many">, {
129
+ id: string;
130
+ name: string;
131
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
132
+ items: ({
133
+ kind: "room";
134
+ id: string;
135
+ } | {
136
+ kind: "user";
137
+ id: string;
138
+ })[];
139
+ }[], {
140
+ id: string;
141
+ name: string;
142
+ icon: "bell" | "bookmark" | "briefcase" | "bug" | "code" | "coffee" | "flag" | "folder" | "globe" | "heart" | "home" | "inbox" | "lightbulb" | "lock" | "megaphone" | "package" | "rocket" | "shield" | "sparkles" | "star" | "tag" | "target" | "users" | "zap" | null;
143
+ items: ({
144
+ kind: "room";
145
+ id: string;
146
+ } | {
147
+ kind: "user";
148
+ id: string;
149
+ })[];
150
+ }[]>;
151
+ export type SidebarSectionItem = z.infer<typeof sidebarSectionItemSchema>;
152
+ export type SidebarSection = z.infer<typeof sidebarSectionSchema>;
153
+ export type SidebarSections = z.infer<typeof sidebarSectionsSchema>;
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ // TAS-633: личные секции сайдбара. Человек собирает свои группы («Клиенты»,
3
+ // «Релиз 0.70») и кладёт в них каналы и людей вперемешку — как в Slack.
4
+ // Секции личные и живут на сервере (WorkspaceMember.sidebarSections), чтобы
5
+ // порядок и состав совпадали на всех устройствах. Пространства на них не
6
+ // влияют. Свёрнутость секции сервер НЕ хранит — это состояние браузера.
7
+ /**
8
+ * Ключи иконок секции. Значение — ключ, а не имя компонента: набор иконок в
9
+ * вебе может смениться, а сохранённые в БД профили — нет.
10
+ */
11
+ export const SECTION_ICONS = [
12
+ "folder",
13
+ "star",
14
+ "heart",
15
+ "flag",
16
+ "bookmark",
17
+ "tag",
18
+ "users",
19
+ "briefcase",
20
+ "rocket",
21
+ "target",
22
+ "zap",
23
+ "bug",
24
+ "code",
25
+ "lightbulb",
26
+ "bell",
27
+ "coffee",
28
+ "globe",
29
+ "home",
30
+ "inbox",
31
+ "lock",
32
+ "megaphone",
33
+ "package",
34
+ "shield",
35
+ "sparkles",
36
+ ];
37
+ export const sectionIconSchema = z.enum(SECTION_ICONS);
38
+ /** Элемент секции: канал или человек. Порядок в массиве — порядок показа. */
39
+ export const sidebarSectionItemSchema = z.discriminatedUnion("kind", [
40
+ z.object({ kind: z.literal("room"), id: z.string().min(1).max(64) }),
41
+ z.object({ kind: z.literal("user"), id: z.string().min(1).max(64) }),
42
+ ]);
43
+ export const MAX_SECTIONS = 20;
44
+ export const MAX_ITEMS_PER_SECTION = 100;
45
+ export const sidebarSectionSchema = z.object({
46
+ id: z.string().min(1).max(40),
47
+ name: z.string().trim().min(1).max(40),
48
+ icon: sectionIconSchema.nullable(),
49
+ items: z.array(sidebarSectionItemSchema).max(MAX_ITEMS_PER_SECTION),
50
+ });
51
+ /**
52
+ * Весь набор секций. Уникальность проверяется здесь, а не на роуте: элемент
53
+ * живёт РОВНО в одном месте — иначе один канал показывается в двух секциях и
54
+ * из общего списка исчезает дважды. Поэтому пара kind+id уникальна СРАЗУ ПО
55
+ * ВСЕМ секциям, а не внутри одной.
56
+ */
57
+ export const sidebarSectionsSchema = z
58
+ .array(sidebarSectionSchema)
59
+ .max(MAX_SECTIONS)
60
+ .superRefine((sections, ctx) => {
61
+ const sectionIds = new Set();
62
+ const seenItems = new Set();
63
+ sections.forEach((section, sectionIndex) => {
64
+ if (sectionIds.has(section.id)) {
65
+ ctx.addIssue({
66
+ code: z.ZodIssueCode.custom,
67
+ path: [sectionIndex, "id"],
68
+ message: "duplicate_section_id",
69
+ });
70
+ }
71
+ sectionIds.add(section.id);
72
+ section.items.forEach((item, itemIndex) => {
73
+ const key = `${item.kind}:${item.id}`;
74
+ if (seenItems.has(key)) {
75
+ ctx.addIssue({
76
+ code: z.ZodIssueCode.custom,
77
+ path: [sectionIndex, "items", itemIndex],
78
+ message: "duplicate_item",
79
+ });
80
+ }
81
+ seenItems.add(key);
82
+ });
83
+ });
84
+ });
85
+ //# sourceMappingURL=sidebarSections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebarSections.js","sourceRoot":"","sources":["../src/sidebarSections.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/shared",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "description": "Shared types, Zod schemas and design tokens for the Taskless API contract — consumed by @taskless/api, @taskless/web and the Taskless mobile app.",
6
6
  "license": "MIT",