twenty-bells 1.0.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/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # The Bell
2
+
3
+ An activity feed inside Twenty. A bell button in the top-right corner opens a side panel showing what happened in the CRM, what your team is talking about, and what is overdue on you personally.
4
+
5
+ Русская версия: [README.ru.md](https://github.com/frasimah/twenty_bells/blob/main/README.ru.md).
6
+
7
+ ## What it does
8
+
9
+ - **Change feed.** Records created and edited, read from the standard `timelineActivity` — custom objects included. Field changes render as a diff, and `SELECT` values keep the same coloured labels the record page uses.
10
+ - **Documents.** Twenty emits no timeline event for an attachment, so the panel reads files separately and files each one into the card of the record it hangs on — a deal, a contact, a custom object. Clicking the file name opens the attachment; clicking anywhere else on the card opens the record.
11
+ - **Buzz.** Team notes as posts with their comments underneath. Twenty has no comment object — amending a note's body is the mechanism — so each thread is reconstructed from the `note.updated` diffs, and every reply says who wrote it and when.
12
+ - **Tasks.** Overdue first, oldest first, then everything else by due date. Done tasks are dropped. If a task belongs to a deal, the deal is on the row. The badge counts what is assigned to you and not done — asked of the server, so it is the real number rather than the length of the loaded page.
13
+ - **Grouping.** Several events on one record collapse into a single line with a counter that expands in place. A burst of identical events — an import, a bulk edit — becomes one row like `29 × document · added`.
14
+ - **Read / unread.** The marker is personal, stored per `userId` in the app's own object.
15
+ - **Opens the record.** A click opens the object in Twenty's side panel without leaving the page.
16
+
17
+ ## What it costs to keep open
18
+
19
+ Nothing at all until the bell is clicked. The panel is a side panel and Twenty loads it on demand — the renderer is not even fetched before that, and closing the panel unmounts the whole thing.
20
+
21
+ Open, it polls once a minute, and every tick asks one cheap question first: the id of the newest event and the size of the window, one row without relations, about 2 KB. Unchanged means the tab already holds the truth and nothing else is fetched. Five minutes without a click, a keypress or a scroll inside the panel and it stops asking altogether, saying so in a line at the top; the next touch resumes it and refreshes at once.
22
+
23
+ On a workspace of some 1,800 tasks and as many notes that comes to roughly 10 KB a minute while somebody is actually reading it.
24
+
25
+ The trade is latency: a new event surfaces within a minute rather than instantly.
26
+
27
+ ## Requirements
28
+
29
+ - Twenty server **2.23.0 or later** (declared as `engines.twenty`)
30
+ - Node 24 and Yarn 4 for local development
31
+
32
+ ## Install into your Twenty
33
+
34
+ ```bash
35
+ git clone https://github.com/frasimah/twenty_bells.git
36
+ cd twenty_bells
37
+ corepack enable
38
+ yarn install
39
+ ```
40
+
41
+ You need an API key for the target instance: in Twenty open **Settings → APIs → Create API key** and copy the value — it is shown once.
42
+
43
+ ```bash
44
+ yarn twenty remote:add --as my-crm --url https://your-twenty-server.com --api-key <YOUR_KEY>
45
+ yarn twenty remote:use my-crm
46
+ yarn twenty apply
47
+ ```
48
+
49
+ `--url` is the **server** address, not the frontend, if the two are on different ports. The key is stored in `~/.twenty/config.json` and never reaches the repository.
50
+
51
+ `apply` prints the plan first, then syncs the application, its role, the technical `feedReadState` object, the front component and the command-menu button. Reload the CRM tab afterwards — the bell appears in the top-right corner.
52
+
53
+ No instance of your own? `yarn twenty docker:start` brings one up on `http://localhost:2020` (`tim@apple.dev` / `tim@apple.dev`), and `remote:add --local` finds it by itself.
54
+
55
+ Updating after a `git pull` is the same `yarn twenty apply`. To remove the app, `yarn twenty app:uninstall`.
56
+
57
+ ## Permissions
58
+
59
+ The panel calls the API under the **application's** role, not under the permissions of the person who opened it. So:
60
+
61
+ - on the **Organization** plan row-level permissions apply — Twenty scopes the data itself and the panel adds no filtering of its own;
62
+ - on other plans the predicates sync but stay inert, and the panel filters client-side: by deal owner, company account owner and task assignee.
63
+
64
+ Which mode is live is detected at runtime: the panel asks the server for records it should have withheld and reads the answer. When Twenty does the scoping itself, the header carries an **Access: server** badge; otherwise the panel filters on its own and says nothing.
65
+
66
+ **The client-side filter is not a security boundary.** Until row-level permissions are enforced, the server hands the whole workspace to the browser. Install the app where that is acceptable.
67
+
68
+ ## Settings
69
+
70
+ The app deliberately ships **no Settings tab**. What would be there — poll interval, page size — lives in `src/front-components/activity-feed.tsx` as constants; change one and run `yarn twenty apply`.
71
+
72
+ > The reason is not minimalism. The host injects application-variable values into a front component still **encrypted** — `enc:v2:<workspace>:<blob>` — and neither `twenty-sdk` nor `twenty-client-sdk` decrypts them. Verified by printing the raw payload inside the panel: `getApplicationVariable('SHOW_ATTACHMENTS')` returned the ciphertext, not `true`, and the panel read it as "off" and hid every file. Switches an admin can move that change nothing are worse than no switches, so they are gone until Twenty decrypts them.
73
+ >
74
+ > This cannot be diagnosed from outside either: the metadata API refuses to read the values back — an API key gets `Cannot return null for non-nullable field Application.applicationVariables`, the application token is denied by permissions. Writing (`updateOneApplicationVariable`) does work.
75
+
76
+ Application variables are workspace-wide anyway; the SDK has no per-user ones. Anything personal lives in the app's `feedReadState` object.
77
+
78
+ ## What the feed cannot show
79
+
80
+ Limits of the source, not of the app:
81
+
82
+ - **deletions** — Twenty writes no event for them at all;
83
+ - **emails and meetings** — `message` and `calendarEvent` have no relation to `timelineActivity`;
84
+ - **history of deleted records** — the event survives but its link breaks; those rows are marked "record deleted".
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ yarn lint # oxlint
90
+ yarn typecheck # tsgo
91
+ yarn test:unit # vitest, no server needed
92
+ yarn test # integration tests against a real instance
93
+ ```
94
+
95
+ CI runs all of it on every push against a Twenty instance spawned for the job. CD is manual (`workflow_dispatch`): the scaffold's deploy-on-push targeted `http://localhost:2020`, which from a GitHub runner is the runner itself.
96
+
97
+ ## Publishing
98
+
99
+ `Publish` (`.github/workflows/publish.yml`) publishes the package to npm with provenance via [npm trusted publishing](https://docs.npmjs.com/trusted-publishers), which is also how app ownership is claimed in a Twenty marketplace.
100
+
101
+ 1. On npmjs.com register this repository and `publish.yml` as a trusted publisher of the package.
102
+ 2. Bump `version` in `package.json`, then push a tag (`git tag v1.0.1 && git push --tags`) or run the workflow from the Actions tab.
103
+
104
+ npm accepts provenance only from **public** repositories. To publish from a private one, set `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` in the publish step — at the cost of the trust badge and of the marketplace ownership claim.
105
+
106
+ The marketplace catalog syncs from npm hourly; `yarn twenty dev:catalog-sync` triggers it immediately.
107
+
108
+ ## Learn more
109
+
110
+ - [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start)
111
+ - [twenty-sdk CLI reference](https://www.npmjs.com/package/twenty-sdk)
112
+ - Changes are listed in [CHANGELOG.md](https://github.com/frasimah/twenty_bells/blob/main/CHANGELOG.md).
package/manifest.json ADDED
@@ -0,0 +1,251 @@
1
+ {
2
+ "application": {
3
+ "universalIdentifier": "95427093-8188-4051-8f40-a94930b08548",
4
+ "displayName": "The Bell",
5
+ "description": "Activity feed, team notes and overdue tasks",
6
+ "author": "kata",
7
+ "category": "Productivity",
8
+ "logo": "public/logo.svg",
9
+ "websiteUrl": "https://kata.agency",
10
+ "emailSupport": "hello@kata.agency",
11
+ "issueReportUrl": "https://github.com/frasimah/twenty_bells/issues",
12
+ "galleryImages": [
13
+ "public/gallery-1-feed.png",
14
+ "public/gallery-2-tasks.png",
15
+ "public/gallery-3-buzz.png"
16
+ ],
17
+ "defaultRoleUniversalIdentifier": "c42e0f90-a77f-41cb-af0e-60273784d211",
18
+ "aboutDescription": "# The Bell\n\nAn activity feed inside Twenty. A bell button in the top-right corner opens a side panel showing what happened in the CRM, what your team is talking about, and what is overdue on you personally.\n\nРусская версия: [README.ru.md](https://github.com/frasimah/twenty_bells/blob/main/README.ru.md).\n\n## What it does\n\n- **Change feed.** Records created and edited, read from the standard `timelineActivity` — custom objects included. Field changes render as a diff, and `SELECT` values keep the same coloured labels the record page uses.\n- **Documents.** Twenty emits no timeline event for an attachment, so the panel reads files separately and files each one into the card of the record it hangs on — a deal, a contact, a custom object. Clicking the file name opens the attachment; clicking anywhere else on the card opens the record.\n- **Buzz.** Team notes as posts with their comments underneath. Twenty has no comment object — amending a note's body is the mechanism — so each thread is reconstructed from the `note.updated` diffs, and every reply says who wrote it and when.\n- **Tasks.** Overdue first, oldest first, then everything else by due date. Done tasks are dropped. If a task belongs to a deal, the deal is on the row. The badge counts what is assigned to you and not done — asked of the server, so it is the real number rather than the length of the loaded page.\n- **Grouping.** Several events on one record collapse into a single line with a counter that expands in place. A burst of identical events — an import, a bulk edit — becomes one row like `29 × document · added`.\n- **Read / unread.** The marker is personal, stored per `userId` in the app's own object.\n- **Opens the record.** A click opens the object in Twenty's side panel without leaving the page.\n\n## What it costs to keep open\n\nNothing at all until the bell is clicked. The panel is a side panel and Twenty loads it on demand — the renderer is not even fetched before that, and closing the panel unmounts the whole thing.\n\nOpen, it polls once a minute, and every tick asks one cheap question first: the id of the newest event and the size of the window, one row without relations, about 2 KB. Unchanged means the tab already holds the truth and nothing else is fetched. Five minutes without a click, a keypress or a scroll inside the panel and it stops asking altogether, saying so in a line at the top; the next touch resumes it and refreshes at once.\n\nOn a workspace of some 1,800 tasks and as many notes that comes to roughly 10 KB a minute while somebody is actually reading it.\n\nThe trade is latency: a new event surfaces within a minute rather than instantly.\n\n## Requirements\n\n- Twenty server **2.23.0 or later** (declared as `engines.twenty`)\n- Node 24 and Yarn 4 for local development\n\n## Install into your Twenty\n\n```bash\ngit clone https://github.com/frasimah/twenty_bells.git\ncd twenty_bells\ncorepack enable\nyarn install\n```\n\nYou need an API key for the target instance: in Twenty open **Settings → APIs → Create API key** and copy the value — it is shown once.\n\n```bash\nyarn twenty remote:add --as my-crm --url https://your-twenty-server.com --api-key <YOUR_KEY>\nyarn twenty remote:use my-crm\nyarn twenty apply\n```\n\n`--url` is the **server** address, not the frontend, if the two are on different ports. The key is stored in `~/.twenty/config.json` and never reaches the repository.\n\n`apply` prints the plan first, then syncs the application, its role, the technical `feedReadState` object, the front component and the command-menu button. Reload the CRM tab afterwards — the bell appears in the top-right corner.\n\nNo instance of your own? `yarn twenty docker:start` brings one up on `http://localhost:2020` (`tim@apple.dev` / `tim@apple.dev`), and `remote:add --local` finds it by itself.\n\nUpdating after a `git pull` is the same `yarn twenty apply`. To remove the app, `yarn twenty app:uninstall`.\n\n## Permissions\n\nThe panel calls the API under the **application's** role, not under the permissions of the person who opened it. So:\n\n- on the **Organization** plan row-level permissions apply — Twenty scopes the data itself and the panel adds no filtering of its own;\n- on other plans the predicates sync but stay inert, and the panel filters client-side: by deal owner, company account owner and task assignee.\n\nWhich mode is live is detected at runtime: the panel asks the server for records it should have withheld and reads the answer. When Twenty does the scoping itself, the header carries an **Access: server** badge; otherwise the panel filters on its own and says nothing.\n\n**The client-side filter is not a security boundary.** Until row-level permissions are enforced, the server hands the whole workspace to the browser. Install the app where that is acceptable.\n\n## Settings\n\nThe app deliberately ships **no Settings tab**. What would be there — poll interval, page size — lives in `src/front-components/activity-feed.tsx` as constants; change one and run `yarn twenty apply`.\n\n> The reason is not minimalism. The host injects application-variable values into a front component still **encrypted** — `enc:v2:<workspace>:<blob>` — and neither `twenty-sdk` nor `twenty-client-sdk` decrypts them. Verified by printing the raw payload inside the panel: `getApplicationVariable('SHOW_ATTACHMENTS')` returned the ciphertext, not `true`, and the panel read it as \"off\" and hid every file. Switches an admin can move that change nothing are worse than no switches, so they are gone until Twenty decrypts them.\n>\n> This cannot be diagnosed from outside either: the metadata API refuses to read the values back — an API key gets `Cannot return null for non-nullable field Application.applicationVariables`, the application token is denied by permissions. Writing (`updateOneApplicationVariable`) does work.\n\nApplication variables are workspace-wide anyway; the SDK has no per-user ones. Anything personal lives in the app's `feedReadState` object.\n\n## What the feed cannot show\n\nLimits of the source, not of the app:\n\n- **deletions** — Twenty writes no event for them at all;\n- **emails and meetings** — `message` and `calendarEvent` have no relation to `timelineActivity`;\n- **history of deleted records** — the event survives but its link breaks; those rows are marked \"record deleted\".\n\n## Development\n\n```bash\nyarn lint # oxlint\nyarn typecheck # tsgo\nyarn test:unit # vitest, no server needed\nyarn test # integration tests against a real instance\n```\n\nCI runs all of it on every push against a Twenty instance spawned for the job. CD is manual (`workflow_dispatch`): the scaffold's deploy-on-push targeted `http://localhost:2020`, which from a GitHub runner is the runner itself.\n\n## Publishing\n\n`Publish` (`.github/workflows/publish.yml`) publishes the package to npm with provenance via [npm trusted publishing](https://docs.npmjs.com/trusted-publishers), which is also how app ownership is claimed in a Twenty marketplace.\n\n1. On npmjs.com register this repository and `publish.yml` as a trusted publisher of the package.\n2. Bump `version` in `package.json`, then push a tag (`git tag v1.0.1 && git push --tags`) or run the workflow from the Actions tab.\n\nnpm accepts provenance only from **public** repositories. To publish from a private one, set `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` in the publish step — at the cost of the trust badge and of the marketplace ownership claim.\n\nThe marketplace catalog syncs from npm hourly; `yarn twenty dev:catalog-sync` triggers it immediately.\n\n## Learn more\n\n- [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start)\n- [twenty-sdk CLI reference](https://www.npmjs.com/package/twenty-sdk)\n- Changes are listed in [CHANGELOG.md](https://github.com/frasimah/twenty_bells/blob/main/CHANGELOG.md).\n",
19
+ "yarnLockChecksum": "5fdce6f28140aad07baeb584f638c172",
20
+ "packageJsonChecksum": "fcfb3cd67ba5ec922b68ecb390724ae6",
21
+ "requiredServerVersionRange": ">=2.23.0"
22
+ },
23
+ "objects": [
24
+ {
25
+ "universalIdentifier": "0f619b61-82c0-410a-83ff-c586c16f0bff",
26
+ "nameSingular": "feedReadState",
27
+ "namePlural": "feedReadStates",
28
+ "labelSingular": "Feed Read State",
29
+ "labelPlural": "Feed Read States",
30
+ "description": "Per-user last seen marker for the activity feed",
31
+ "icon": "IconEye",
32
+ "fields": [
33
+ {
34
+ "universalIdentifier": "10195d66-dbbe-433d-ac8a-6b4b76acbcfc",
35
+ "name": "userId",
36
+ "type": "TEXT",
37
+ "label": "User id",
38
+ "icon": "IconUser"
39
+ },
40
+ {
41
+ "universalIdentifier": "8bfb613d-43f5-4869-965b-8a569290a9ae",
42
+ "name": "lastSeenAt",
43
+ "type": "DATE_TIME",
44
+ "label": "Last seen at",
45
+ "icon": "IconClock"
46
+ },
47
+ {
48
+ "name": "name",
49
+ "label": "Name",
50
+ "description": "Name",
51
+ "icon": "IconAbc",
52
+ "isNullable": true,
53
+ "defaultValue": null,
54
+ "type": "TEXT",
55
+ "universalIdentifier": "fd898ce0-fac3-5c74-a697-1b74dae77dc9"
56
+ }
57
+ ],
58
+ "labelIdentifierFieldMetadataUniversalIdentifier": "fd898ce0-fac3-5c74-a697-1b74dae77dc9"
59
+ }
60
+ ],
61
+ "fields": [],
62
+ "indexes": [],
63
+ "permissionFlags": [],
64
+ "roles": [
65
+ {
66
+ "universalIdentifier": "c42e0f90-a77f-41cb-af0e-60273784d211",
67
+ "label": "The Bell default function role",
68
+ "description": "The Bell default function role",
69
+ "canReadAllObjectRecords": true,
70
+ "canUpdateAllObjectRecords": false,
71
+ "canSoftDeleteAllObjectRecords": false,
72
+ "canDestroyAllObjectRecords": false,
73
+ "objectPermissions": [
74
+ {
75
+ "objectUniversalIdentifier": "0f619b61-82c0-410a-83ff-c586c16f0bff",
76
+ "canReadObjectRecords": true,
77
+ "canUpdateObjectRecords": true,
78
+ "canSoftDeleteObjectRecords": false,
79
+ "canDestroyObjectRecords": false,
80
+ "universalIdentifier": "f8688503-ae7f-56fd-8df0-e32a0005230f"
81
+ }
82
+ ],
83
+ "rowLevelPermissionPredicates": [
84
+ {
85
+ "universalIdentifier": "7a5b1d02-6f43-4c8e-9a21-3d5e8c4b2f10",
86
+ "objectUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
87
+ "fieldUniversalIdentifier": "20202020-be7e-4d1e-8e19-3d5c7c4b9f2a",
88
+ "operand": "IS",
89
+ "workspaceMemberFieldUniversalIdentifier": "d77dff76-dcfd-52a3-b352-832898b28b9d"
90
+ },
91
+ {
92
+ "universalIdentifier": "2c9f4b83-51ad-4e26-9f0b-6a7d1e3c8b44",
93
+ "objectUniversalIdentifier": "20202020-b374-4779-a561-80086cb2e17f",
94
+ "fieldUniversalIdentifier": "20202020-95b8-4e10-9881-edb5d4765f9d",
95
+ "operand": "IS",
96
+ "workspaceMemberFieldUniversalIdentifier": "d77dff76-dcfd-52a3-b352-832898b28b9d"
97
+ },
98
+ {
99
+ "universalIdentifier": "e41d7a65-9c3b-4f18-8d52-0b6e2f9a7c31",
100
+ "objectUniversalIdentifier": "20202020-1ba1-48ba-bc83-ef7e5990ed10",
101
+ "fieldUniversalIdentifier": "20202020-065a-4f42-a906-e20422c1753f",
102
+ "operand": "IS",
103
+ "workspaceMemberFieldUniversalIdentifier": "d77dff76-dcfd-52a3-b352-832898b28b9d"
104
+ }
105
+ ],
106
+ "fieldPermissions": [],
107
+ "rowLevelPermissionPredicateGroups": [],
108
+ "permissionFlagUniversalIdentifiers": []
109
+ }
110
+ ],
111
+ "skills": [],
112
+ "agents": [],
113
+ "connectionProviders": [],
114
+ "logicFunctions": [],
115
+ "frontComponents": [
116
+ {
117
+ "universalIdentifier": "f3947d6f-f605-43a3-817e-281216b367b6",
118
+ "name": "activity-feed",
119
+ "description": "Workspace activity feed built on the standard timelineActivity",
120
+ "componentName": "ActivityFeed",
121
+ "sourceComponentPath": "src/front-components/activity-feed.tsx",
122
+ "builtComponentPath": "src/front-components/activity-feed.mjs",
123
+ "builtComponentChecksum": "5d4ffce5a0105e486f4d0485a69f1664dea51e7d0ba0ea61cfdbe0c0cc26173f",
124
+ "isHeadless": false,
125
+ "usesSdkClient": false
126
+ }
127
+ ],
128
+ "publicAssets": [
129
+ {
130
+ "filePath": "public/gallery-1-feed.png",
131
+ "fileName": "gallery-1-feed.png",
132
+ "fileType": "png",
133
+ "checksum": "30085327a36506c1975f946ec43af230"
134
+ },
135
+ {
136
+ "filePath": "public/gallery-2-tasks.png",
137
+ "fileName": "gallery-2-tasks.png",
138
+ "fileType": "png",
139
+ "checksum": "d07c65079bd8345277d3a8f465ace1ca"
140
+ },
141
+ {
142
+ "filePath": "public/gallery-3-buzz.png",
143
+ "fileName": "gallery-3-buzz.png",
144
+ "fileType": "png",
145
+ "checksum": "29baaa6ede9d2a4ef018019901fdb815"
146
+ },
147
+ {
148
+ "filePath": "public/logo.svg",
149
+ "fileName": "logo.svg",
150
+ "fileType": "svg",
151
+ "checksum": "eae29937c4ff2c9103cd4b33085c92d3"
152
+ }
153
+ ],
154
+ "views": [],
155
+ "viewFields": [],
156
+ "navigationMenuItems": [],
157
+ "pageLayouts": [],
158
+ "pageLayoutTabs": [],
159
+ "commandMenuItems": [
160
+ {
161
+ "universalIdentifier": "9e1b2fb1-8ff3-4742-9358-4d425fbb769c",
162
+ "label": "The Bell",
163
+ "icon": "IconBell",
164
+ "shortLabel": "The Bell",
165
+ "isPinned": true,
166
+ "availabilityType": "GLOBAL",
167
+ "frontComponentUniversalIdentifier": "f3947d6f-f605-43a3-817e-281216b367b6"
168
+ }
169
+ ],
170
+ "translations": {
171
+ "ru-RU": {
172
+ "SDBm/z": "ещё 1 комментарий",
173
+ "/9AFM1": "ещё 1 событие по этой записи",
174
+ "BNg5+K": "Права: сервер",
175
+ "MMAUm4": "На вас и не завершено: {count}",
176
+ "4YE4Qc": "По сроку",
177
+ "H86f9p": "Свернуть",
178
+ "7i8j3G": "Компания",
179
+ "vwsXGm": "Не удалось загрузить ленту: {error}",
180
+ "7Zdnlq": "Документ",
181
+ "5oD9f/": "Ранее",
182
+ "8YihB7": "Всё, что изменилось в рабочем пространстве.",
183
+ "DBO7c2": "Лента",
184
+ "0clZX0": "Отметка прочтения",
185
+ "DFgabi": "Отметки прочтения",
186
+ "qKywYm": "Загрузить предыдущую страницу событий",
187
+ "Pwqkdw": "Загружаю…",
188
+ "JSxZVX": "Прочитано",
189
+ "stgyHs": "Отметить все события прочитанными",
190
+ "isRobC": "Новое",
191
+ "p7FfSa": "Заметок пока нет.",
192
+ "PBQOVr": "Открытых задач нет.",
193
+ "KiJn9B": "Заметка",
194
+ "/UWbfN": "Пока ничего не происходило. Создайте или измените любую запись — событие появится здесь.",
195
+ "1+Oxi9": "Открыть заметку",
196
+ "P2o9xF": "Открыть задачу",
197
+ "8FzTxb": "Открытые задачи: сначала просроченные, дальше по сроку.",
198
+ "cmvr2/": "Открыть: {label}",
199
+ "SV/iis": "Сделка",
200
+ "ddrz1m": "Просрочено",
201
+ "y3hQNM": "Просрочено: {count}",
202
+ "eBGNxQ": "Персональная отметка прочтения для ленты",
203
+ "OZdaTZ": "Контакт",
204
+ "w80YWM": "Запись",
205
+ "AxtbvG": "Row-level permissions включены: доступ ограничивает сам Twenty, панель ничего не доотфильтровывает.",
206
+ "+Cg9pL": "Просмотрено {loaded} из {total} обновлений рабочего пространства за последние {days} дней",
207
+ "fMPkxb": "Показать ещё",
208
+ "6edDJh": "Показать записи",
209
+ "fk7pBo": "Показать, к какому месту это написано",
210
+ "C6+ZRl": "Кто-то",
211
+ "Q3P/4s": "Задача",
212
+ "GtycJ/": "Задачи",
213
+ "mL/Lgk": "Заметки команды и комментарии к ним.",
214
+ "dxrv8L": "The Bell",
215
+ "INSpH5": "Автор неизвестен",
216
+ "wja8aL": "Без названия",
217
+ "ia8YsC": "Ближайшие",
218
+ "XTdpcb": "Обновления на паузе — панель простаивает. Нажмите, чтобы продолжить.",
219
+ "kdh3Fk": "добавлен",
220
+ "6jYXIg": "дописано: {text}",
221
+ "GBvSln": "массовое изменение",
222
+ "6yQlea": "комментарий",
223
+ "wn8BKW": "создана",
224
+ "YulJZd": "удалена",
225
+ "XH30C4": "документ",
226
+ "k4b5/X": "изменён",
227
+ "LfQPqi": "через {days} дн",
228
+ "xOTzt5": "только что",
229
+ "3LUngZ": "без изменений",
230
+ "jvRUwM": "без срока",
231
+ "QHiY/N": "запись удалена, не открыть",
232
+ "2T3/dQ": "откреплён",
233
+ "5XqX1f": "переписано: {text}",
234
+ "3b4nbV": "переписал текст",
235
+ "4O3LPI": "задача",
236
+ "bWx82j": "текст добавлен: {text}",
237
+ "5D2tMA": "текст удалён",
238
+ "v2pM8o": "текст сокращён",
239
+ "X0OwOB": "сегодня",
240
+ "x4Xp3c": "изменена",
241
+ "gKh0dY": "ещё {count} событий по этой записи",
242
+ "Froj7p": "{count} обновлений по вашим записям за неделю",
243
+ "FezTR0": "{count} × {object}",
244
+ "XWEqQj": "{days} дн",
245
+ "TK9yAS": "просрочено на {days} дн",
246
+ "dzoeuc": "ещё {hidden} комментариев",
247
+ "/oqig9": "{hours} ч",
248
+ "ZOtn9F": "{minutes} мин"
249
+ }
250
+ }
251
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "twenty-bells",
3
+ "version": "1.0.0",
4
+ "description": "The Bell — an activity feed for Twenty: what changed in the workspace, team notes with their comments, documents, and the tasks that are overdue.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "kata",
8
+ "email": "hello@kata.agency",
9
+ "url": "https://kata.agency"
10
+ },
11
+ "homepage": "https://github.com/frasimah/twenty_bells#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/frasimah/twenty_bells.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/frasimah/twenty_bells/issues"
18
+ },
19
+ "engines": {
20
+ "node": "^24.5.0",
21
+ "npm": "please-use-yarn",
22
+ "yarn": ">=4.0.2",
23
+ "twenty": ">=2.23.0"
24
+ },
25
+ "keywords": [
26
+ "twenty-app",
27
+ "twenty",
28
+ "crm",
29
+ "activity-feed",
30
+ "notifications"
31
+ ],
32
+ "packageManager": "yarn@4.13.0",
33
+ "scripts": {
34
+ "twenty": "twenty",
35
+ "lint": "oxlint -c .oxlintrc.json .",
36
+ "lint:fix": "oxlint --fix -c .oxlintrc.json .",
37
+ "typecheck": "tsgo --noEmit -p tsconfig.spec.json",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "test:unit": "vitest run --config vitest.unit.config.ts"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.7.2",
44
+ "@types/react": "^19.0.0",
45
+ "@typescript/native-preview": "^7.0.0-dev.20260116.1",
46
+ "oxlint": "^0.16.0",
47
+ "react": "^19.0.0",
48
+ "react-dom": "^19.0.0",
49
+ "twenty-client-sdk": "2.23.0",
50
+ "twenty-sdk": "2.23.0",
51
+ "twenty-ui": "1.0.0-alpha.1",
52
+ "typescript": "^5.9.3",
53
+ "vite-tsconfig-paths": "^4.2.1",
54
+ "vitest": "^4.0.0"
55
+ }
56
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
2
+ <rect width="80" height="80" rx="16" fill="#141414"/>
3
+ <g
4
+ transform="translate(16,16) scale(2)"
5
+ fill="none"
6
+ stroke="#FAFAFA"
7
+ stroke-width="1.8"
8
+ stroke-linecap="round"
9
+ stroke-linejoin="round"
10
+ >
11
+ <path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" />
12
+ <path d="M9 17v1a3 3 0 0 0 6 0v-1" />
13
+ </g>
14
+ </svg>