mnfst 0.5.166 → 0.5.167

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.
@@ -3215,29 +3215,50 @@ function mergeRoles(manifestRoles, userGeneratedRoles) {
3215
3215
  return merged;
3216
3216
  }
3217
3217
 
3218
- // Normalize custom roles for Appwrite (add "owner" if any role requires it)
3218
+ // Appwrite membership role tokens allow only [a-zA-Z0-9._-]. Role definitions are
3219
+ // keyed by display name (which may contain spaces/unicode), so we store an
3220
+ // Appwrite-safe slug on the membership and resolve by matching slug(token) against
3221
+ // slug(defName). Idempotent, and "owner" (Appwrite's intrinsic role) passes through.
3222
+ function roleSlug(name) {
3223
+ if (name === 'owner') return 'owner';
3224
+ return String(name)
3225
+ .trim()
3226
+ .toLowerCase()
3227
+ .replace(/[^a-z0-9._-]+/g, '-')
3228
+ .replace(/^-+|-+$/g, '');
3229
+ }
3230
+
3231
+ // Normalize custom roles for Appwrite: slugify tokens (so space/unicode names stay
3232
+ // assignable) and add "owner" if any role requires it.
3219
3233
  function normalizeRolesForAppwrite(customRoles, memberRoles, userGeneratedRoles = null) {
3220
3234
  if (!Array.isArray(customRoles)) {
3221
3235
  return customRoles;
3222
3236
  }
3223
3237
 
3238
+ const slugged = customRoles.map(role => roleSlug(role));
3239
+
3224
3240
  // Merge manifest and user-generated roles
3225
3241
  const allRoles = mergeRoles(memberRoles, userGeneratedRoles);
3226
3242
 
3227
- // If no roles config, return as-is (will use Appwrite's default behavior)
3243
+ // If no roles config, return slugged tokens (Appwrite handles owner default)
3228
3244
  if (!allRoles || Object.keys(allRoles).length === 0) {
3229
- return customRoles;
3245
+ return slugged;
3230
3246
  }
3231
3247
 
3232
- // Check if any custom role requires owner
3233
- const requiresOwner = customRoles.some(role => roleRequiresOwner(role, allRoles));
3248
+ // Which definition slugs require owner checked by slug so this works whether the
3249
+ // incoming tokens are display names or already slugged.
3250
+ const ownerSlugs = new Set();
3251
+ for (const name of Object.keys(allRoles)) {
3252
+ if (roleRequiresOwner(name, allRoles)) ownerSlugs.add(roleSlug(name));
3253
+ }
3254
+ const requiresOwner = slugged.some(s => ownerSlugs.has(s));
3234
3255
 
3235
3256
  // If owner is already in the list, don't duplicate
3236
- if (requiresOwner && !customRoles.includes('owner')) {
3237
- return [...customRoles, 'owner'];
3257
+ if (requiresOwner && !slugged.includes('owner')) {
3258
+ return [...slugged, 'owner'];
3238
3259
  }
3239
3260
 
3240
- return customRoles;
3261
+ return slugged;
3241
3262
  }
3242
3263
 
3243
3264
  // Normalize Appwrite roles for display (filter "owner" if a custom role replaces it)
@@ -3249,10 +3270,15 @@ function normalizeRolesForDisplay(appwriteRoles, memberRoles, userGeneratedRoles
3249
3270
  // Merge manifest and user-generated roles
3250
3271
  const allRoles = mergeRoles(memberRoles, userGeneratedRoles);
3251
3272
 
3252
- // If custom roles are defined, always filter out "owner" (it's a background Appwrite role)
3253
- // "owner" is automatically added by Appwrite for permissions, but shouldn't be displayed
3273
+ // If custom roles are defined, filter out "owner" (a background Appwrite role) and
3274
+ // translate slug tokens back to their display names (legacy exact-name tokens map
3275
+ // through slug() too; unknown tokens pass through as-is).
3254
3276
  if (allRoles && Object.keys(allRoles).length > 0) {
3255
- return appwriteRoles.filter(role => role !== 'owner');
3277
+ const bySlug = {};
3278
+ for (const name of Object.keys(allRoles)) bySlug[roleSlug(name)] = name;
3279
+ return appwriteRoles
3280
+ .filter(role => role !== 'owner')
3281
+ .map(role => bySlug[roleSlug(role)] || role);
3256
3282
  }
3257
3283
 
3258
3284
  // If no custom roles config, show "owner" as-is (legacy behavior)
@@ -3303,9 +3329,13 @@ function hasPermission(userRoles, permission, memberRoles, userGeneratedRoles =
3303
3329
  return true;
3304
3330
  }
3305
3331
 
3306
- // Check if any of the user's custom roles has this permission
3332
+ // Check if any of the user's custom roles has this permission. Match by slug so
3333
+ // display-name definitions resolve against slugged membership tokens, and legacy
3334
+ // exact-name tokens still resolve.
3335
+ const bySlug = {};
3336
+ for (const [name, perms] of Object.entries(allRoles)) bySlug[roleSlug(name)] = perms;
3307
3337
  for (const roleName of customRoles) {
3308
- const rolePermissions = allRoles[roleName];
3338
+ const rolePermissions = bySlug[roleSlug(roleName)];
3309
3339
  if (rolePermissions && Array.isArray(rolePermissions) && rolePermissions.includes(permission)) {
3310
3340
  return true;
3311
3341
  }
@@ -3360,6 +3390,10 @@ function initializeTeamsRoles() {
3360
3390
  return getOwnerPermissions();
3361
3391
  };
3362
3392
 
3393
+ store.roleSlug = function (name) {
3394
+ return roleSlug(name);
3395
+ };
3396
+
3363
3397
  store.validateRoleConfig = async function () {
3364
3398
  const appwriteConfig = await config.getAppwriteConfig();
3365
3399
  const memberRoles = appwriteConfig?.memberRoles || null;
@@ -3532,6 +3566,7 @@ window.ManifestAppwriteAuthTeamsRoles = {
3532
3566
  roleHasAllOwnerPermissions,
3533
3567
  getUserGeneratedRoles,
3534
3568
  mergeRoles,
3569
+ roleSlug,
3535
3570
  normalizeRolesForAppwrite,
3536
3571
  normalizeRolesForDisplay,
3537
3572
  getPrimaryDisplayRole,
@@ -5446,7 +5481,19 @@ function initializeTeamsConvenience() {
5446
5481
  const userMembership = this.currentTeamMemberships.find(
5447
5482
  m => m.userId === this.user.$id
5448
5483
  );
5449
- return userMembership?.roles || [];
5484
+ const raw = userMembership?.roles || [];
5485
+ // Membership tokens are Appwrite-safe slugs; translate them back to the
5486
+ // author's display names using the cached role map. "owner" and unknown
5487
+ // tokens pass through unchanged.
5488
+ const slug = this.roleSlug;
5489
+ const allRoles = (this.allTeamRoles && this.allTeamRoles(this.currentTeam)) || {};
5490
+ const keys = Object.keys(allRoles);
5491
+ if (typeof slug !== 'function' || !keys.length) {
5492
+ return raw;
5493
+ }
5494
+ const bySlug = {};
5495
+ for (const name of keys) bySlug[slug(name)] = name;
5496
+ return raw.map(r => (r === 'owner' ? 'owner' : (bySlug[slug(r)] || r)));
5450
5497
  };
5451
5498
 
5452
5499
  // Check if current user has a specific permission in the current team
@@ -5494,9 +5541,13 @@ function initializeTeamsConvenience() {
5494
5541
  }
5495
5542
 
5496
5543
  // Granted if any of the user's custom roles includes this permission
5497
- // (built-in or custom key).
5544
+ // (built-in or custom key). Match by slug so slugged/legacy membership
5545
+ // tokens resolve against display-name definitions.
5546
+ const slug = this.roleSlug || (x => x);
5547
+ const bySlug = {};
5548
+ for (const [name, perms] of Object.entries(allRoles)) bySlug[slug(name)] = perms;
5498
5549
  for (const roleName of customRoles) {
5499
- const rolePermissions = allRoles[roleName];
5550
+ const rolePermissions = bySlug[slug(roleName)];
5500
5551
  if (Array.isArray(rolePermissions) && rolePermissions.includes(permission)) {
5501
5552
  return true;
5502
5553
  }
@@ -5510,7 +5561,8 @@ function initializeTeamsConvenience() {
5510
5561
  return false;
5511
5562
  }
5512
5563
  const userRoles = this.getCurrentTeamRoles();
5513
- return userRoles.includes(roleName);
5564
+ const slug = this.roleSlug || (x => x);
5565
+ return userRoles.some(r => slug(r) === slug(roleName));
5514
5566
  };
5515
5567
 
5516
5568
  // Get current user's primary role in current team
@@ -3,7 +3,7 @@
3
3
  /* https://manifestx.dev
4
4
  /*
5
5
  /* Handle = reactive conversation view fed by an adapter; drives intents. The
6
- /* plugin transports/stores nothing — see CHAT-PLUGIN-DESIGN.md.
6
+ /* plugin transports/stores nothing — see templates/chat-adapter/README.md.
7
7
  */
8
8
 
9
9
  (function () {
@@ -287,8 +287,11 @@
287
287
  function mk(cid, channel, parts, msgs, closed) {
288
288
  convs.set(cid, { id: cid, channel, closed: !!closed, participants: parts, messages: msgs });
289
289
  }
290
- const m = (cid, author, text, minsAgo, extra) =>
291
- Object.assign({ id: id('m'), conversationId: cid, author, body: { text }, ts: t(minsAgo), status: 'delivered' }, extra || {});
290
+ const m = (cid, author, text, minsAgo, extra) => {
291
+ const msg = Object.assign({ id: id('m'), conversationId: cid, author, body: { text }, ts: t(minsAgo), status: 'delivered' }, extra || {});
292
+ if (msg.media) { msg.body.media = msg.media; delete msg.media; } // media lives on body
293
+ return msg;
294
+ };
292
295
 
293
296
  // 1:1 AI co-pilot
294
297
  mk('dm-ai', 'webchat', [me, bot], [
@@ -296,6 +299,26 @@
296
299
  m('dm-ai', me, 'How many free credits do I get?', 11)
297
300
  ]);
298
301
 
302
+ // Attachment showcase — one of every media kind, both directions
303
+ // (harness-only conversation; asset paths resolve in the test project)
304
+ mk('media-1', 'webchat', [me, bot], [
305
+ m('media-1', me, 'Here is the artwork and the brief.', 22, {
306
+ media: [
307
+ { kind: 'image', url: '/test/media/sample.png', name: 'artwork.png', mediaType: 'image/png' },
308
+ { kind: 'document', url: '/test/media/sample.pdf', name: 'brief.pdf', mediaType: 'application/pdf' }
309
+ ]
310
+ }),
311
+ m('media-1', bot, 'Received — and here is everything back in every format:', 20, {
312
+ media: [
313
+ { kind: 'image', url: '/test/media/sample.png', name: 'render.png', mediaType: 'image/png' },
314
+ { kind: 'audio', url: '/test/media/sample.wav', name: 'jingle.wav', mediaType: 'audio/wav' },
315
+ { kind: 'voice', url: '/test/media/sample.wav', name: 'voice note', mediaType: 'audio/wav' },
316
+ { kind: 'video', url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4', name: 'clip.mp4', mediaType: 'video/mp4' },
317
+ { kind: 'document', url: '/test/media/sample.pdf', name: 'summary.pdf', mediaType: 'application/pdf' }
318
+ ]
319
+ })
320
+ ]);
321
+
299
322
  // group with reactions + a small reply tree
300
323
  const g1 = m('grp-1', ana, 'Ship the v2 doc today?', 30);
301
324
  mk('grp-1', 'webchat', [me, ana, bo], [
@@ -378,7 +401,20 @@
378
401
  setTimeout(() => deliver(target, msg), 60); // server echo (reconciles media/status by id)
379
402
  return { id: msg.id, ts: msg.ts, conversationId: target.id };
380
403
  },
381
- async react(cid, mid, emoji) { const c = backend.convs.get(cid); const msg = c && c.messages.find(x => x.id === mid); if (msg) { msg.reactions = [{ emoji, count: 1, byMe: true, by: [backend.me.id] }]; emit(cid, h => h.onReaction && h.onReaction(mid, msg.reactions)); } return { ok: true }; },
404
+ async react(cid, mid, emoji) {
405
+ // toggle: off if I reacted, join if others did, add if new
406
+ const c = backend.convs.get(cid); const msg = c && c.messages.find(x => x.id === mid);
407
+ if (msg) {
408
+ const rs = (msg.reactions || []).map(r => Object.assign({}, r));
409
+ const r = rs.find(x => x.emoji === emoji);
410
+ if (r && r.byMe) { r.count--; r.byMe = false; }
411
+ else if (r) { r.count++; r.byMe = true; }
412
+ else rs.push({ emoji, count: 1, byMe: true });
413
+ msg.reactions = rs.filter(x => x.count > 0);
414
+ emit(cid, h => h.onReaction && h.onReaction(mid, msg.reactions));
415
+ }
416
+ return { ok: true };
417
+ },
382
418
  async addParticipant(cid, p) { const c = backend.convs.get(cid); if (c && !c.participants.some(x => x.id === p.id)) { c.participants.push(p); emit(cid, h => h.onParticipant && h.onParticipant(p, 'added')); } return { ok: true }; },
383
419
  async transfer(cid, from, to, role) { emit(cid, h => h.onParticipant && h.onParticipant({ id: to, role: role || 'assignee' }, 'changed')); return { ok: true }; },
384
420
  async setTyping(cid, on) { emit(cid, h => h.onTyping && h.onTyping(backend.me.id, on)); },
@@ -413,11 +449,11 @@
413
449
 
414
450
  // ---- simulation hooks (button-driven; deterministic for verification) ---
415
451
  const sim = {
416
- // stream an AI reply token-by-token into a conversation
417
- aiReply(cid, text) {
452
+ // stream an AI reply token-by-token into a conversation (media attaches on completion)
453
+ aiReply(cid, text, media) {
418
454
  const c = backend.convs.get(cid); if (!c) return;
419
455
  const mid = backend.id('m');
420
- const msg = { id: mid, conversationId: cid, author: backend.bot, body: { text: '' }, ts: backend.t(0), status: 'streaming', meta: { authoredBy: 'u_me' } };
456
+ const msg = { id: mid, conversationId: cid, author: backend.bot, body: { text: '', media: media || undefined }, ts: backend.t(0), status: 'streaming', meta: { authoredBy: 'u_me' } };
421
457
  c.messages.push(msg);
422
458
  emit(cid, h => h.onMessage && h.onMessage(msg));
423
459
  const words = (text || 'Every account starts with 5,000 free credits each month.').split(' ');
@@ -482,7 +518,8 @@
482
518
  // Same-origin relay mnfst-run serves for an `ai` block; override via
483
519
  // opts.endpoint / window.CHAT_LLM_ENDPOINT.
484
520
  const endpoint = opts.endpoint || window.CHAT_LLM_ENDPOINT || '/_ai/chat';
485
- const system = opts.system || 'You are a helpful assistant for the Manifest framework docs. Answer in concise markdown.';
521
+ // only when set the relay's ai block (system + grounding) is the default
522
+ const system = opts.system || undefined;
486
523
  const handlers = {}; // conversationId -> subscribe handlers
487
524
  let seq = 0;
488
525
  const id = (p) => p + '_' + Date.now().toString(36) + '_' + (++seq);
@@ -498,12 +535,15 @@
498
535
  const toMsg = (r) => ({ id: r.id, conversationId: null, author: r.role === 'assistant' ? BOT : USER, body: { text: r.text }, ts: r.ts, status: 'delivered' });
499
536
 
500
537
  // Build the Anthropic messages array; attachments → image/document blocks.
538
+ // Only what the API accepts is forwarded (images + PDFs with data);
539
+ // audio/video/voice render locally but don't reach the model.
501
540
  function apiMessages(cid, draft) {
502
541
  const hist = loadStore(cid).map(r => ({ role: r.role, content: r.text }));
503
- const media = (draft.body && draft.body.media) || [];
542
+ const media = ((draft.body && draft.body.media) || []).filter(a =>
543
+ a.data && (a.kind === 'image' || a.mediaType === 'application/pdf'));
504
544
  const blocks = media.map(a => a.kind === 'image'
505
545
  ? { type: 'image', source: { type: 'base64', media_type: a.mediaType, data: a.data } }
506
- : { type: 'document', source: { type: 'base64', media_type: a.mediaType || 'application/pdf', data: a.data } });
546
+ : { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: a.data } });
507
547
  const text = (draft.body && draft.body.text) || draft.text || '';
508
548
  const last = blocks.length ? { role: 'user', content: [...blocks, { type: 'text', text }] } : { role: 'user', content: text };
509
549
  return [...hist, last];
@@ -555,6 +595,129 @@
555
595
  })();
556
596
 
557
597
 
598
+ /* Manifest Chat — Appwrite adapter (conversations as app data)
599
+ * By Andrew Matlock under MIT license
600
+ * https://manifestx.dev
601
+ *
602
+ * Messages live as rows in an Appwrite table; inbound arrives over Appwrite
603
+ * Realtime; identity comes from $auth (guest sessions cover anonymous use).
604
+ * Built for comments, support threads, and small-group chat — high-volume
605
+ * messaging belongs on a purpose-built bus behind a custom adapter.
606
+ *
607
+ * Config: opts { databaseId, tableId, ttlHours } or manifest.json
608
+ * chat: { appwriteDatabaseId, appwriteTableId, ttlHours }.
609
+ * Table columns: conversationId (indexed) · text · authorId · authorName ·
610
+ * authorColor? · replyTo? — ts/id come from $createdAt/$id.
611
+ */
612
+
613
+ (function () {
614
+ 'use strict';
615
+
616
+ function ready(fn) {
617
+ if (window.ManifestChatAdapters) return fn();
618
+ const t = setInterval(() => { if (window.ManifestChatAdapters) { clearInterval(t); fn(); } }, 20);
619
+ setTimeout(() => clearInterval(t), 5000);
620
+ }
621
+
622
+ ready(function () {
623
+ function hashColor(id) {
624
+ let h = 0; for (const ch of String(id)) h = (h * 31 + ch.charCodeAt(0)) >>> 0;
625
+ return `hsl(${h % 360} 60% 45%)`;
626
+ }
627
+
628
+ function appwriteAdapter(opts) {
629
+ opts = opts || {};
630
+ let cfg = null;
631
+
632
+ async function config() {
633
+ if (cfg) return cfg;
634
+ let databaseId = opts.databaseId, tableId = opts.tableId, ttlHours = opts.ttlHours;
635
+ if ((!databaseId || !tableId) && window.ManifestDataConfig && window.ManifestDataConfig.ensureManifest) {
636
+ const m = await window.ManifestDataConfig.ensureManifest();
637
+ const c = (m && m.chat) || {};
638
+ databaseId = databaseId || c.appwriteDatabaseId;
639
+ tableId = tableId || c.appwriteTableId;
640
+ if (ttlHours == null) ttlHours = c.ttlHours;
641
+ }
642
+ if (!databaseId || !tableId) console.warn('[Manifest Chat] appwrite adapter needs databaseId + tableId (opts or manifest.json chat block).');
643
+ cfg = { databaseId, tableId, ttlHours };
644
+ return cfg;
645
+ }
646
+
647
+ function me() {
648
+ const auth = window.Alpine ? window.Alpine.store('auth') : null;
649
+ const u = auth && auth.user;
650
+ if (u && u.$id) return { id: u.$id, kind: 'human', displayName: u.name || u.email || 'Guest', color: hashColor(u.$id) };
651
+ let gid = null;
652
+ try { gid = localStorage.getItem('mnfst.chat.gid'); if (!gid) { gid = 'g_' + Math.random().toString(36).slice(2, 10); localStorage.setItem('mnfst.chat.gid', gid); } } catch (_) { gid = 'g_anon'; }
653
+ return { id: gid, kind: 'human', displayName: 'Guest', color: hashColor(gid) };
654
+ }
655
+
656
+ function toMsg(row) {
657
+ return {
658
+ id: row.$id, conversationId: row.conversationId,
659
+ author: { id: row.authorId, kind: 'human', displayName: row.authorName, color: row.authorColor || hashColor(row.authorId) },
660
+ body: { text: row.text }, replyTo: row.replyTo || undefined,
661
+ ts: row.$createdAt, status: 'delivered'
662
+ };
663
+ }
664
+
665
+ return {
666
+ identity: () => me(),
667
+
668
+ async load(cid) {
669
+ const { databaseId, tableId, ttlHours } = await config();
670
+ if (!databaseId) return { messages: [], participants: [] };
671
+ // tablesDB directly (not loadTableRows): public read("any") tables
672
+ // must load before any session exists — no auth gate on reads.
673
+ const services = await window.ManifestDataAppwrite.getAppwriteDataServices();
674
+ const Q = window.Appwrite.Query;
675
+ const queries = [Q.equal('conversationId', cid), Q.orderDesc('$createdAt'), Q.limit(100)];
676
+ if (ttlHours) queries.push(Q.greaterThan('$createdAt', new Date(Date.now() - ttlHours * 3600e3).toISOString()));
677
+ const res = await services.tablesDB.listRows({ databaseId, tableId, queries });
678
+ return { messages: res.rows.map(toMsg).reverse(), participants: [] };
679
+ },
680
+
681
+ subscribe(cid, handlers) {
682
+ let unsub = null, closed = false;
683
+ (async () => {
684
+ const { databaseId, tableId } = await config();
685
+ if (!databaseId) return;
686
+ const services = await window.ManifestDataAppwrite.getAppwriteDataServices();
687
+ if (!services || !services.realtime || closed) return;
688
+ unsub = services.realtime.subscribe(`databases.${databaseId}.tables.${tableId}.rows`, (res) => {
689
+ if (!res || !res.payload || res.payload.conversationId !== cid) return;
690
+ const events = Array.isArray(res.events) ? res.events : [res.events];
691
+ if (events.some(e => typeof e === 'string' && e.endsWith('.create'))) handlers.onMessage(toMsg(res.payload));
692
+ });
693
+ if (handlers.onConnection) handlers.onConnection(true);
694
+ })();
695
+ return () => { closed = true; try { unsub && unsub(); } catch (_) { } };
696
+ },
697
+
698
+ async send(cid, draft) {
699
+ const { databaseId, tableId } = await config();
700
+ const auth = window.Alpine ? window.Alpine.store('auth') : null;
701
+ if (auth && !auth.isAuthenticated && typeof auth.requestGuest === 'function') {
702
+ try { await auth.requestGuest(); } catch (_) { } // guests may create
703
+ }
704
+ const who = me();
705
+ const text = (draft.body && draft.body.text) || draft.text || '';
706
+ const row = await window.ManifestDataAppwrite.createRow(databaseId, tableId, {
707
+ conversationId: cid, text,
708
+ authorId: who.id, authorName: who.displayName, authorColor: who.color,
709
+ replyTo: draft.replyTo || null
710
+ });
711
+ return { id: row.$id, ts: row.$createdAt };
712
+ }
713
+ };
714
+ }
715
+
716
+ window.ManifestChatAdapters.register('appwrite', appwriteAdapter);
717
+ });
718
+ })();
719
+
720
+
558
721
  /* Manifest Chat — magic + init
559
722
  /* By Andrew Matlock under MIT license
560
723
  /* https://manifestx.dev
@@ -72,16 +72,25 @@
72
72
  gap: var(--spacing, 0.25rem);
73
73
  max-width: 100%;
74
74
  height: calc(var(--spacing-field-height, 2.25rem) - var(--spacing, 0.25rem) * 2.5);
75
+ margin: 0;
76
+ padding: 0;
75
77
  padding-inline-start: calc(var(--spacing, 0.25rem) * 2);
76
78
  font-size: 0.875rem;
79
+ line-height: normal;
77
80
  color: var(--color-content-stark, oklch(43.9% 0 0));
78
81
  background-color: var(--color-popover-surface, oklch(100% 0 0));
82
+ border: 0;
79
83
  border-radius: calc(var(--radius, 0.5rem) * 0.7);
80
84
  overflow: hidden;
81
85
  user-select: none;
82
86
 
83
87
  /* Label */
84
88
  &>span {
89
+ min-width: 0;
90
+ margin: 0;
91
+ padding: 0;
92
+ border: 0;
93
+ font: inherit;
85
94
  overflow: hidden;
86
95
  text-overflow: ellipsis;
87
96
  white-space: nowrap
@@ -92,15 +101,23 @@
92
101
  display: inline-flex;
93
102
  align-items: center;
94
103
  justify-content: center;
104
+ box-sizing: border-box;
105
+ flex: none;
106
+ width: auto !important;
95
107
  min-width: 0;
108
+ max-width: none;
96
109
  height: 100%;
110
+ min-height: 0;
97
111
  aspect-ratio: 1 / 1;
112
+ margin: 0;
98
113
  padding: 0;
114
+ border: 0;
115
+ border-radius: 0;
99
116
  font-size: 1em;
100
117
  line-height: 1;
101
118
  color: var(--color-content-neutral, oklch(43.9% 0 0));
102
119
  background: transparent;
103
- border-radius: 0;
120
+ box-shadow: none;
104
121
  cursor: pointer;
105
122
 
106
123
  &:hover {