@tthr/vue 0.0.8 → 0.0.10

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/dist/index.d.ts CHANGED
@@ -65,6 +65,22 @@ export interface MutationFunction<TArgs = void, TResult = unknown> {
65
65
  _args?: TArgs;
66
66
  _result?: TResult;
67
67
  }
68
+ /**
69
+ * Options for useMutation
70
+ */
71
+ export interface UseMutationOptions {
72
+ /**
73
+ * Query names to invalidate after mutation succeeds.
74
+ * This will trigger a refetch for all active subscriptions matching these queries.
75
+ *
76
+ * @example
77
+ * // When creating a comment, also refresh the posts list to update comment counts
78
+ * const createComment = useMutation(api.comments.create, {
79
+ * invalidates: ['posts.list', 'posts.byId']
80
+ * });
81
+ */
82
+ invalidates?: string[];
83
+ }
68
84
  /**
69
85
  * Mutation composable
70
86
  *
@@ -76,11 +92,16 @@ export interface MutationFunction<TArgs = void, TResult = unknown> {
76
92
  *
77
93
  * const createPost = useMutation(api.posts.create);
78
94
  * await createPost.mutate({ title: 'Hello', content: '...' });
95
+ *
96
+ * // With query invalidation - refreshes posts list when a comment is added
97
+ * const createComment = useMutation(api.comments.create, {
98
+ * invalidates: ['posts.list']
99
+ * });
79
100
  * </script>
80
101
  * ```
81
102
  */
82
- export declare function useMutation<TArgs, TResult>(mutation: MutationFunction<TArgs, TResult>): MutationState<TArgs, TResult>;
83
- export type { TetherClientOptions } from '@tthr/client';
103
+ export declare function useMutation<TArgs, TResult>(mutation: MutationFunction<TArgs, TResult>, options?: UseMutationOptions): MutationState<TArgs, TResult>;
104
+ export type { TetherClientOptions, MutationOptions } from '@tthr/client';
84
105
  /**
85
106
  * Tether Nuxt/Vue project configuration
86
107
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA+B,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKtE;;GAEG;AACH,eAAO,MAAM,YAAY;iBACV,GAAG,WAAW,mBAAmB;uBAK3B,mBAAmB;CAGvC,CAAC;AAEF;;GAEG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAKxC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,OAAO;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EACrC,KAAK,EAAE,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,EACpC,IAAI,CAAC,EAAE,KAAK,GACX,UAAU,CAAC,OAAO,CAAC,CA4CrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,EAAE,OAAO;IAC3C,IAAI,EAAE,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC/B,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,OAAO;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EACxC,QAAQ,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,GACzC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAmC/B;AAGD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA+B,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKtE;;GAEG;AACH,eAAO,MAAM,YAAY;iBACV,GAAG,WAAW,mBAAmB;uBAK3B,mBAAmB;CAGvC,CAAC;AAEF;;GAEG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAKxC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,OAAO;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EACrC,KAAK,EAAE,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,EACpC,IAAI,CAAC,EAAE,KAAK,GACX,UAAU,CAAC,OAAO,CAAC,CA4CrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,EAAE,OAAO;IAC3C,IAAI,EAAE,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC/B,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,OAAO;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EACxC,QAAQ,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC1C,OAAO,CAAC,EAAE,kBAAkB,GAC3B,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAqC/B;AAGD,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
package/dist/index.js CHANGED
@@ -92,10 +92,15 @@ export function useQuery(query, args) {
92
92
  *
93
93
  * const createPost = useMutation(api.posts.create);
94
94
  * await createPost.mutate({ title: 'Hello', content: '...' });
95
+ *
96
+ * // With query invalidation - refreshes posts list when a comment is added
97
+ * const createComment = useMutation(api.comments.create, {
98
+ * invalidates: ['posts.list']
99
+ * });
95
100
  * </script>
96
101
  * ```
97
102
  */
98
- export function useMutation(mutation) {
103
+ export function useMutation(mutation, options) {
99
104
  const data = ref();
100
105
  const error = ref(null);
101
106
  const isPending = ref(false);
@@ -104,7 +109,9 @@ export function useMutation(mutation) {
104
109
  isPending.value = true;
105
110
  error.value = null;
106
111
  const client = useTether();
107
- const result = await client.mutation(mutation._name, args);
112
+ const result = await client.mutation(mutation._name, args, {
113
+ invalidates: options?.invalidates,
114
+ });
108
115
  data.value = result;
109
116
  return result;
110
117
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -53,21 +53,84 @@
53
53
  v-for="post in posts.data.value"
54
54
  :key="post.id"
55
55
  class="tether-welcome__post"
56
+ :class="{ 'tether-welcome__post--expanded': expandedPostId === post.id }"
56
57
  >
57
- <div class="tether-welcome__post-content">
58
- <h3>{{ post.title }}</h3>
59
- <time>{{ formatDate(post.createdAt) }}</time>
58
+ <div class="tether-welcome__post-header">
59
+ <div class="tether-welcome__post-content">
60
+ <h3>{{ post.title }}</h3>
61
+ <time>{{ formatDate(post.createdAt) }}</time>
62
+ </div>
63
+ <div class="tether-welcome__post-actions">
64
+ <button
65
+ class="tether-welcome__post-comment-toggle"
66
+ @click="toggleComments(post.id)"
67
+ title="Toggle comments"
68
+ >
69
+ <svg viewBox="0 0 20 20" fill="currentColor">
70
+ <path fill-rule="evenodd" d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z" clip-rule="evenodd" />
71
+ </svg>
72
+ <span v-if="getCommentCount(post.id) > 0">{{ getCommentCount(post.id) }}</span>
73
+ </button>
74
+ <button
75
+ class="tether-welcome__post-delete"
76
+ @click="handleDeletePost(post.id)"
77
+ :disabled="deletePost.isPending.value"
78
+ title="Delete post"
79
+ >
80
+ <svg viewBox="0 0 20 20" fill="currentColor">
81
+ <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
82
+ </svg>
83
+ </button>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Comments section -->
88
+ <div v-if="expandedPostId === post.id" class="tether-welcome__comments">
89
+ <form class="tether-welcome__comment-form" @submit.prevent="handleCreateComment(post.id)">
90
+ <input
91
+ v-model="newCommentText"
92
+ type="text"
93
+ placeholder="Add a comment..."
94
+ class="tether-welcome__comment-input"
95
+ :disabled="createComment.isPending.value"
96
+ />
97
+ <button
98
+ type="submit"
99
+ class="tether-welcome__comment-submit"
100
+ :disabled="!newCommentText.trim() || createComment.isPending.value"
101
+ >
102
+ <svg viewBox="0 0 20 20" fill="currentColor">
103
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 1.414L10.586 9H7a1 1 0 100 2h3.586l-1.293 1.293a1 1 0 101.414 1.414l3-3a1 1 0 000-1.414z" clip-rule="evenodd" />
104
+ </svg>
105
+ </button>
106
+ </form>
107
+
108
+ <div class="tether-welcome__comment-list">
109
+ <div
110
+ v-for="comment in postComments[post.id] || []"
111
+ :key="comment.id"
112
+ class="tether-welcome__comment"
113
+ >
114
+ <p>{{ comment.content }}</p>
115
+ <div class="tether-welcome__comment-meta">
116
+ <time>{{ formatDate(comment.createdAt) }}</time>
117
+ <button
118
+ class="tether-welcome__comment-delete"
119
+ @click="handleDeleteComment(comment.id, post.id)"
120
+ :disabled="deleteComment.isPending.value"
121
+ title="Delete comment"
122
+ >
123
+ <svg viewBox="0 0 20 20" fill="currentColor">
124
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
125
+ </svg>
126
+ </button>
127
+ </div>
128
+ </div>
129
+ <p v-if="!postComments[post.id]?.length" class="tether-welcome__no-comments">
130
+ No comments yet
131
+ </p>
132
+ </div>
60
133
  </div>
61
- <button
62
- class="tether-welcome__post-delete"
63
- @click="handleDeletePost(post.id)"
64
- :disabled="deletePost.isPending.value"
65
- title="Delete post"
66
- >
67
- <svg viewBox="0 0 20 20" fill="currentColor">
68
- <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
69
- </svg>
70
- </button>
71
134
  </article>
72
135
  </template>
73
136
  <p v-else class="tether-welcome__empty">
@@ -117,18 +180,116 @@ interface Post {
117
180
  updatedAt: string;
118
181
  }
119
182
 
183
+ interface Comment {
184
+ id: string;
185
+ postId: string;
186
+ content: string;
187
+ authorId: string;
188
+ createdAt: string;
189
+ }
190
+
120
191
  const newPostTitle = ref('');
192
+ const expandedPostId = ref<string | null>(null);
193
+ const newCommentText = ref('');
194
+ const postComments = ref<Record<string, Comment[]>>({});
121
195
 
122
196
  // Set up queries and mutations (these use SSR-safe server routes)
123
197
  const posts = useQuery<void, Post[]>('posts.list');
124
198
  const createPost = useMutation<{ title: string; content?: string }, { id: string }>('posts.create');
125
199
  const deletePost = useMutation<{ id: string }, void>('posts.remove');
200
+ const createComment = useMutation<{ postId: string; content: string }, { id: string }>('comments.create');
201
+ const deleteComment = useMutation<{ id: string }, void>('comments.remove');
126
202
 
127
203
  // Set up WebSocket subscription for realtime updates (client-side only)
128
- const { isConnected } = useTetherSubscription('posts.list', undefined, () => {
129
- posts.refetch();
204
+ // Uses setData to hot-swap data without full reload
205
+ const { isConnected } = useTetherSubscription('posts.list', undefined, (data) => {
206
+ if (data && Array.isArray(data)) {
207
+ posts.setData(data as Post[]);
208
+ }
209
+ });
210
+
211
+ // Subscribe to comments updates
212
+ useTetherSubscription('comments.list', undefined, (data) => {
213
+ if (data && Array.isArray(data)) {
214
+ // Group comments by postId
215
+ const grouped: Record<string, Comment[]> = {};
216
+ for (const comment of data as Comment[]) {
217
+ if (!grouped[comment.postId]) {
218
+ grouped[comment.postId] = [];
219
+ }
220
+ grouped[comment.postId].push(comment);
221
+ }
222
+ postComments.value = grouped;
223
+ }
130
224
  });
131
225
 
226
+ async function toggleComments(postId: string) {
227
+ if (expandedPostId.value === postId) {
228
+ expandedPostId.value = null;
229
+ } else {
230
+ expandedPostId.value = postId;
231
+ // Load comments for this post if not already loaded
232
+ if (!postComments.value[postId]) {
233
+ try {
234
+ const response = await $fetch<{ data: Comment[] }>('/api/_tether/query', {
235
+ method: 'POST',
236
+ body: {
237
+ function: 'comments.listByPost',
238
+ args: { postId },
239
+ },
240
+ });
241
+ postComments.value[postId] = response.data || [];
242
+ } catch {
243
+ postComments.value[postId] = [];
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ async function handleCreateComment(postId: string) {
250
+ if (!newCommentText.value.trim()) return;
251
+
252
+ try {
253
+ await createComment.mutate({
254
+ postId,
255
+ content: newCommentText.value.trim(),
256
+ });
257
+ newCommentText.value = '';
258
+ // Refresh comments for this post
259
+ const response = await $fetch<{ data: Comment[] }>('/api/_tether/query', {
260
+ method: 'POST',
261
+ body: {
262
+ function: 'comments.listByPost',
263
+ args: { postId },
264
+ },
265
+ });
266
+ postComments.value[postId] = response.data || [];
267
+ } catch (error) {
268
+ console.error('Failed to create comment:', error);
269
+ }
270
+ }
271
+
272
+ async function handleDeleteComment(commentId: string, postId: string) {
273
+ try {
274
+ await deleteComment.mutate({ id: commentId });
275
+ // Refresh comments for this post
276
+ const response = await $fetch<{ data: Comment[] }>('/api/_tether/query', {
277
+ method: 'POST',
278
+ body: {
279
+ function: 'comments.listByPost',
280
+ args: { postId },
281
+ },
282
+ });
283
+ postComments.value[postId] = response.data || [];
284
+ } catch (error) {
285
+ console.error('Failed to delete comment:', error);
286
+ }
287
+ }
288
+
289
+ function getCommentCount(postId: string): number {
290
+ return postComments.value[postId]?.length || 0;
291
+ }
292
+
132
293
  async function handleCreatePost() {
133
294
  if (!newPostTitle.value.trim()) return;
134
295
 
@@ -165,9 +326,15 @@ function formatDate(dateString: string): string {
165
326
  }
166
327
  </script>
167
328
 
329
+ <style>
330
+ *, *::before, *::after { box-sizing: border-box; }
331
+ body { padding: 0; margin: 0; height: 100dvh; }
332
+ #__nuxt { height: 100dvh; }
333
+ </style>
334
+
168
335
  <style scoped>
169
336
  .tether-welcome {
170
- min-height: 100vh;
337
+ min-height: 100dvh;
171
338
  background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
172
339
  color: #e4e4e7;
173
340
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -200,10 +367,7 @@ function formatDate(dateString: string): string {
200
367
  font-size: 2.5rem;
201
368
  font-weight: 700;
202
369
  margin: 0;
203
- background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
204
- -webkit-background-clip: text;
205
- -webkit-text-fill-color: transparent;
206
- background-clip: text;
370
+ color: #f4f4f5;
207
371
  }
208
372
 
209
373
  .tether-welcome__subtitle {
@@ -342,20 +506,32 @@ function formatDate(dateString: string): string {
342
506
 
343
507
  .tether-welcome__post {
344
508
  display: flex;
345
- align-items: center;
346
- justify-content: space-between;
347
- gap: 1rem;
348
- padding: 0.875rem 1rem;
509
+ flex-direction: column;
510
+ gap: 0;
511
+ padding: 0;
349
512
  background: rgba(0, 0, 0, 0.2);
350
513
  border: 1px solid rgba(255, 255, 255, 0.05);
351
514
  border-radius: 0.5rem;
352
515
  transition: background 0.2s;
516
+ overflow: hidden;
353
517
  }
354
518
 
355
519
  .tether-welcome__post:hover {
356
520
  background: rgba(0, 0, 0, 0.3);
357
521
  }
358
522
 
523
+ .tether-welcome__post--expanded {
524
+ background: rgba(0, 0, 0, 0.3);
525
+ }
526
+
527
+ .tether-welcome__post-header {
528
+ display: flex;
529
+ align-items: center;
530
+ justify-content: space-between;
531
+ gap: 1rem;
532
+ padding: 0.875rem 1rem;
533
+ }
534
+
359
535
  .tether-welcome__post-content {
360
536
  flex: 1;
361
537
  min-width: 0;
@@ -376,6 +552,36 @@ function formatDate(dateString: string): string {
376
552
  color: #71717a;
377
553
  }
378
554
 
555
+ .tether-welcome__post-actions {
556
+ display: flex;
557
+ gap: 0.25rem;
558
+ }
559
+
560
+ .tether-welcome__post-comment-toggle {
561
+ flex-shrink: 0;
562
+ display: flex;
563
+ align-items: center;
564
+ gap: 0.25rem;
565
+ padding: 0.375rem 0.5rem;
566
+ background: transparent;
567
+ border: none;
568
+ border-radius: 0.375rem;
569
+ color: #71717a;
570
+ font-size: 0.75rem;
571
+ cursor: pointer;
572
+ transition: color 0.2s, background 0.2s;
573
+ }
574
+
575
+ .tether-welcome__post-comment-toggle:hover {
576
+ color: #a1a1aa;
577
+ background: rgba(255, 255, 255, 0.05);
578
+ }
579
+
580
+ .tether-welcome__post-comment-toggle svg {
581
+ width: 16px;
582
+ height: 16px;
583
+ }
584
+
379
585
  .tether-welcome__post-delete {
380
586
  flex-shrink: 0;
381
587
  width: 32px;
@@ -406,6 +612,133 @@ function formatDate(dateString: string): string {
406
612
  height: 18px;
407
613
  }
408
614
 
615
+ /* Comments styles */
616
+ .tether-welcome__comments {
617
+ padding: 0.75rem 1rem 1rem;
618
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
619
+ background: rgba(0, 0, 0, 0.15);
620
+ }
621
+
622
+ .tether-welcome__comment-form {
623
+ display: flex;
624
+ gap: 0.5rem;
625
+ }
626
+
627
+ .tether-welcome__comment-input {
628
+ flex: 1;
629
+ padding: 0.5rem 0.75rem;
630
+ background: rgba(0, 0, 0, 0.3);
631
+ border: 1px solid rgba(255, 255, 255, 0.1);
632
+ border-radius: 0.375rem;
633
+ color: #e4e4e7;
634
+ font-size: 0.8125rem;
635
+ outline: none;
636
+ transition: border-color 0.2s, box-shadow 0.2s;
637
+ }
638
+
639
+ .tether-welcome__comment-input:focus {
640
+ border-color: #6366f1;
641
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
642
+ }
643
+
644
+ .tether-welcome__comment-input::placeholder {
645
+ color: #71717a;
646
+ }
647
+
648
+ .tether-welcome__comment-submit {
649
+ display: flex;
650
+ align-items: center;
651
+ justify-content: center;
652
+ width: 32px;
653
+ height: 32px;
654
+ background: #6366f1;
655
+ border: none;
656
+ border-radius: 0.375rem;
657
+ color: white;
658
+ cursor: pointer;
659
+ transition: opacity 0.2s;
660
+ }
661
+
662
+ .tether-welcome__comment-submit:hover:not(:disabled) {
663
+ opacity: 0.9;
664
+ }
665
+
666
+ .tether-welcome__comment-submit:disabled {
667
+ opacity: 0.5;
668
+ cursor: not-allowed;
669
+ }
670
+
671
+ .tether-welcome__comment-submit svg {
672
+ width: 16px;
673
+ height: 16px;
674
+ }
675
+
676
+ .tether-welcome__comment-list {
677
+ display: flex;
678
+ flex-direction: column;
679
+ gap: 0.5rem;
680
+ padding-top: 0.75rem;
681
+ }
682
+
683
+ .tether-welcome__comment {
684
+ padding: 0.5rem 0.75rem;
685
+ background: rgba(255, 255, 255, 0.03);
686
+ border-radius: 0.375rem;
687
+ }
688
+
689
+ .tether-welcome__comment p {
690
+ margin: 0 0 0.25rem 0;
691
+ font-size: 0.8125rem;
692
+ color: #e4e4e7;
693
+ }
694
+
695
+ .tether-welcome__comment-meta {
696
+ display: flex;
697
+ align-items: center;
698
+ justify-content: space-between;
699
+ }
700
+
701
+ .tether-welcome__comment-meta time {
702
+ font-size: 0.6875rem;
703
+ color: #71717a;
704
+ }
705
+
706
+ .tether-welcome__comment-delete {
707
+ display: flex;
708
+ align-items: center;
709
+ justify-content: center;
710
+ width: 20px;
711
+ height: 20px;
712
+ background: transparent;
713
+ border: none;
714
+ border-radius: 0.25rem;
715
+ color: #71717a;
716
+ cursor: pointer;
717
+ transition: color 0.2s;
718
+ }
719
+
720
+ .tether-welcome__comment-delete:hover:not(:disabled) {
721
+ color: #ef4444;
722
+ }
723
+
724
+ .tether-welcome__comment-delete:disabled {
725
+ opacity: 0.5;
726
+ cursor: not-allowed;
727
+ }
728
+
729
+ .tether-welcome__comment-delete svg {
730
+ width: 12px;
731
+ height: 12px;
732
+ }
733
+
734
+ .tether-welcome__no-comments {
735
+ margin: 0;
736
+ padding: 0.5rem;
737
+ text-align: center;
738
+ font-size: 0.75rem;
739
+ color: #71717a;
740
+ }
741
+
409
742
  .tether-welcome__links {
410
743
  display: flex;
411
744
  justify-content: center;
@@ -16,6 +16,8 @@ export interface QueryState<T> {
16
16
  error: Ref<Error | null>;
17
17
  isLoading: Ref<boolean>;
18
18
  refetch: () => Promise<void>;
19
+ /** Update data directly (used for hot-swapping from WebSocket) */
20
+ setData: (newData: T) => void;
19
21
  }
20
22
 
21
23
  /**
@@ -77,11 +79,17 @@ export function useQuery<TArgs, TResult>(
77
79
  fetchData();
78
80
  }
79
81
 
82
+ // Direct data setter for hot-swapping from WebSocket
83
+ const setData = (newData: TResult) => {
84
+ data.value = newData;
85
+ };
86
+
80
87
  return {
81
88
  data: data as Ref<TResult | undefined>,
82
89
  error,
83
90
  isLoading,
84
91
  refetch: fetchData,
92
+ setData,
85
93
  };
86
94
  }
87
95
 
@@ -37,7 +37,7 @@ export default defineEventHandler(async (event) => {
37
37
  });
38
38
  }
39
39
 
40
- const response = await fetch(`${url}/api/v1/${projectId}/mutation`, {
40
+ const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
41
41
  method: 'POST',
42
42
  headers: {
43
43
  'Content-Type': 'application/json',
@@ -37,7 +37,7 @@ export default defineEventHandler(async (event) => {
37
37
  });
38
38
  }
39
39
 
40
- const response = await fetch(`${url}/api/v1/${projectId}/query`, {
40
+ const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
41
41
  method: 'POST',
42
42
  headers: {
43
43
  'Content-Type': 'application/json',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tthr/vue",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Tether Vue/Nuxt SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",