create-syncular-app 0.1.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.
@@ -0,0 +1,233 @@
1
+ :root {
2
+ color: #17211f;
3
+ background: #eef2ef;
4
+ font-family:
5
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
6
+ "Segoe UI", sans-serif;
7
+ font-synthesis: none;
8
+ text-rendering: optimizeLegibility;
9
+ }
10
+
11
+ * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ margin: 0;
17
+ min-width: 320px;
18
+ min-height: 100vh;
19
+ }
20
+
21
+ button,
22
+ input {
23
+ font: inherit;
24
+ }
25
+
26
+ .app-shell {
27
+ max-width: 640px;
28
+ min-height: 100vh;
29
+ margin: 0 auto;
30
+ padding: 32px 18px;
31
+ }
32
+
33
+ .topbar {
34
+ margin-bottom: 20px;
35
+ }
36
+
37
+ .eyebrow {
38
+ margin: 0 0 5px;
39
+ color: #65706d;
40
+ font-size: 0.76rem;
41
+ font-weight: 700;
42
+ text-transform: uppercase;
43
+ }
44
+
45
+ h1,
46
+ h2 {
47
+ margin: 0;
48
+ }
49
+
50
+ h1 {
51
+ font-size: clamp(1.8rem, 4vw, 2.6rem);
52
+ line-height: 1.05;
53
+ }
54
+
55
+ h2 {
56
+ font-size: 1.22rem;
57
+ }
58
+
59
+ .client-pane {
60
+ padding: 18px;
61
+ border: 1px solid #d2ddd8;
62
+ border-top: 5px solid #2f8f83;
63
+ border-radius: 8px;
64
+ background: #fbfdfb;
65
+ box-shadow: 0 18px 40px rgb(24 42 36 / 8%);
66
+ }
67
+
68
+ .pane-header {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: space-between;
72
+ gap: 12px;
73
+ margin-bottom: 18px;
74
+ }
75
+
76
+ .status-badge {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: 7px;
80
+ min-width: 88px;
81
+ justify-content: center;
82
+ padding: 7px 9px;
83
+ border-radius: 999px;
84
+ color: #21302c;
85
+ background: #ecf3ef;
86
+ font-size: 0.82rem;
87
+ font-weight: 700;
88
+ }
89
+
90
+ .status-dot {
91
+ width: 8px;
92
+ height: 8px;
93
+ border-radius: 50%;
94
+ background: #77837e;
95
+ }
96
+
97
+ .status-badge.ready .status-dot {
98
+ background: #2f8f83;
99
+ }
100
+
101
+ .status-badge.syncing .status-dot {
102
+ background: #c59724;
103
+ }
104
+
105
+ .status-badge.offline .status-dot {
106
+ background: #5f6f79;
107
+ }
108
+
109
+ .status-badge.attention .status-dot {
110
+ background: #b7791f;
111
+ }
112
+
113
+ .status-badge.error .status-dot {
114
+ background: #c54646;
115
+ }
116
+
117
+ .add-row {
118
+ display: grid;
119
+ grid-template-columns: minmax(0, 1fr) 42px;
120
+ gap: 8px;
121
+ margin-bottom: 14px;
122
+ }
123
+
124
+ .add-row input {
125
+ min-width: 0;
126
+ height: 42px;
127
+ padding: 0 12px;
128
+ border: 1px solid #cbd6d1;
129
+ border-radius: 8px;
130
+ background: #fff;
131
+ color: #17211f;
132
+ }
133
+
134
+ .add-row input:focus {
135
+ border-color: #2f8f83;
136
+ outline: 3px solid rgb(47 143 131 / 18%);
137
+ }
138
+
139
+ .add-row button {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ border: 0;
144
+ border-radius: 8px;
145
+ color: #fff;
146
+ background: #17211f;
147
+ font-size: 1.2rem;
148
+ cursor: pointer;
149
+ }
150
+
151
+ .task-list {
152
+ display: grid;
153
+ gap: 8px;
154
+ }
155
+
156
+ .task-row {
157
+ display: grid;
158
+ grid-template-columns: 34px minmax(0, 1fr) 34px;
159
+ align-items: center;
160
+ gap: 8px;
161
+ min-height: 44px;
162
+ padding: 6px;
163
+ border: 1px solid #dde5e1;
164
+ border-radius: 8px;
165
+ background: #fff;
166
+ }
167
+
168
+ .task-row span {
169
+ min-width: 0;
170
+ overflow-wrap: anywhere;
171
+ font-size: 0.95rem;
172
+ }
173
+
174
+ .task-row span.done {
175
+ color: #7b8581;
176
+ text-decoration: line-through;
177
+ }
178
+
179
+ .icon-button {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ width: 32px;
184
+ height: 32px;
185
+ border: 0;
186
+ border-radius: 8px;
187
+ color: #1f3a35;
188
+ background: #e4efeb;
189
+ cursor: pointer;
190
+ }
191
+
192
+ .icon-button.muted {
193
+ color: #5d6763;
194
+ background: transparent;
195
+ }
196
+
197
+ .icon-button:hover,
198
+ .add-row button:hover {
199
+ filter: brightness(0.94);
200
+ }
201
+
202
+ .empty-state,
203
+ .error-line,
204
+ .offline-line {
205
+ margin: 12px 0 0;
206
+ padding: 12px;
207
+ border-radius: 8px;
208
+ font-size: 0.9rem;
209
+ }
210
+
211
+ .empty-state {
212
+ color: #68736f;
213
+ background: #f2f6f4;
214
+ }
215
+
216
+ .error-line {
217
+ color: #8f2727;
218
+ background: #fff1f1;
219
+ overflow-wrap: anywhere;
220
+ }
221
+
222
+ .offline-line {
223
+ color: #45545b;
224
+ background: #eef4f6;
225
+ overflow-wrap: anywhere;
226
+ }
227
+
228
+ .pane-footnote {
229
+ margin: 14px 0 0;
230
+ color: #68736f;
231
+ font-size: 0.82rem;
232
+ font-weight: 600;
233
+ }
@@ -0,0 +1,23 @@
1
+ import { defineSyncularClient, scope, syncedTable } from '@syncular/typegen';
2
+
3
+ /**
4
+ * Syncular app contract.
5
+ *
6
+ * The schema lives in migrations/*.sql; this file describes how the synced
7
+ * tables map to subscriptions and scopes. After changing either, run
8
+ * `bun run codegen` (or `npx syncular generate`) to refresh the generated
9
+ * modules under src/generated/.
10
+ */
11
+ export const app = defineSyncularClient({
12
+ typescriptOutputPath: 'src/generated/syncular.generated.ts',
13
+ typescriptServerOutputPath: 'src/generated/syncular.server.generated.ts',
14
+ tables: {
15
+ tasks: syncedTable({
16
+ table: 'tasks',
17
+ subscriptionId: 'sub-tasks',
18
+ scopes: [scope('user_id', { source: 'actorId', required: true })],
19
+ serverVersion: 'server_version',
20
+ sqliteWithoutRowid: true,
21
+ }),
22
+ },
23
+ });
@@ -0,0 +1,145 @@
1
+ {
2
+ "$schema": "https://syncular.dev/schemas/syncular.schema.v1.json",
3
+ "contractVersion": 1,
4
+ "appSchemaVersion": 1,
5
+ "clientSchemaSupport": {
6
+ "current": 1,
7
+ "minSupported": 1,
8
+ "supported": [
9
+ 1
10
+ ]
11
+ },
12
+ "migrations": [
13
+ {
14
+ "version": "0001",
15
+ "schemaVersion": 1,
16
+ "name": "initial"
17
+ }
18
+ ],
19
+ "tables": [
20
+ {
21
+ "name": "tasks",
22
+ "primaryKeyColumn": "id",
23
+ "serverVersionColumn": "server_version",
24
+ "softDeleteColumn": null,
25
+ "columns": [
26
+ {
27
+ "name": "id",
28
+ "sqlType": "TEXT",
29
+ "typeFamily": "text",
30
+ "appType": "string",
31
+ "nullable": false,
32
+ "notnullRequired": false,
33
+ "primaryKey": true,
34
+ "hasDefault": false,
35
+ "defaultSql": null,
36
+ "serverVersion": false,
37
+ "softDelete": false,
38
+ "blobRef": false,
39
+ "scope": null
40
+ },
41
+ {
42
+ "name": "title",
43
+ "sqlType": "TEXT",
44
+ "typeFamily": "text",
45
+ "appType": "string",
46
+ "nullable": false,
47
+ "notnullRequired": true,
48
+ "primaryKey": false,
49
+ "hasDefault": false,
50
+ "defaultSql": null,
51
+ "serverVersion": false,
52
+ "softDelete": false,
53
+ "blobRef": false,
54
+ "scope": null
55
+ },
56
+ {
57
+ "name": "completed",
58
+ "sqlType": "INTEGER",
59
+ "typeFamily": "integer",
60
+ "appType": "integer",
61
+ "nullable": false,
62
+ "notnullRequired": true,
63
+ "primaryKey": false,
64
+ "hasDefault": true,
65
+ "defaultSql": "0",
66
+ "serverVersion": false,
67
+ "softDelete": false,
68
+ "blobRef": false,
69
+ "scope": null
70
+ },
71
+ {
72
+ "name": "user_id",
73
+ "sqlType": "TEXT",
74
+ "typeFamily": "text",
75
+ "appType": "string",
76
+ "nullable": false,
77
+ "notnullRequired": true,
78
+ "primaryKey": false,
79
+ "hasDefault": false,
80
+ "defaultSql": null,
81
+ "serverVersion": false,
82
+ "softDelete": false,
83
+ "blobRef": false,
84
+ "scope": "user_id"
85
+ },
86
+ {
87
+ "name": "created_at",
88
+ "sqlType": "BIGINT",
89
+ "typeFamily": "integer",
90
+ "appType": "integer",
91
+ "nullable": false,
92
+ "notnullRequired": true,
93
+ "primaryKey": false,
94
+ "hasDefault": true,
95
+ "defaultSql": "0",
96
+ "serverVersion": false,
97
+ "softDelete": false,
98
+ "blobRef": false,
99
+ "scope": null
100
+ },
101
+ {
102
+ "name": "server_version",
103
+ "sqlType": "BIGINT",
104
+ "typeFamily": "integer",
105
+ "appType": "integer",
106
+ "nullable": false,
107
+ "notnullRequired": true,
108
+ "primaryKey": false,
109
+ "hasDefault": true,
110
+ "defaultSql": "0",
111
+ "serverVersion": true,
112
+ "softDelete": false,
113
+ "blobRef": false,
114
+ "scope": null
115
+ }
116
+ ],
117
+ "blobColumns": [],
118
+ "crdtYjsFields": [],
119
+ "encryptedFields": [],
120
+ "scopes": [
121
+ {
122
+ "name": "user_id",
123
+ "column": "user_id",
124
+ "source": "actorId",
125
+ "required": true
126
+ }
127
+ ],
128
+ "subscription": {
129
+ "id": "sub-tasks",
130
+ "params": {}
131
+ },
132
+ "sqliteWithoutRowid": true
133
+ }
134
+ ],
135
+ "localBaseSchema": {
136
+ "tableSetupSql": [
137
+ "CREATE TABLE IF NOT EXISTS \"tasks\" (\n \"id\" TEXT PRIMARY KEY,\n \"title\" TEXT NOT NULL,\n \"completed\" INTEGER NOT NULL DEFAULT 0,\n \"user_id\" TEXT NOT NULL,\n \"created_at\" INTEGER NOT NULL DEFAULT 0,\n \"server_version\" INTEGER NOT NULL DEFAULT 0\n) WITHOUT ROWID"
138
+ ]
139
+ },
140
+ "localDerivedSchema": {
141
+ "indexes": [],
142
+ "readModelSetupSql": [],
143
+ "readModelRebuildSql": []
144
+ }
145
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2024", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "noEmit": true,
15
+ "types": ["bun", "vite/client"]
16
+ },
17
+ "include": ["src", "scripts", "syncular.app.ts", "vite.config.ts"]
18
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ host: '127.0.0.1',
6
+ port: 5173,
7
+ strictPort: false,
8
+ },
9
+ });