snice 3.6.0 → 3.7.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.
Files changed (60) hide show
  1. package/bin/snice.js +2 -1
  2. package/bin/templates/CLAUDE.md +24 -2
  3. package/bin/templates/pwa/README.md +173 -0
  4. package/bin/templates/pwa/global.d.ts +10 -0
  5. package/bin/templates/pwa/index.html +17 -0
  6. package/bin/templates/pwa/package.json +25 -0
  7. package/bin/templates/pwa/public/icons/.gitkeep +6 -0
  8. package/bin/templates/pwa/public/manifest.json +24 -0
  9. package/bin/templates/pwa/public/vite.svg +1 -0
  10. package/bin/templates/pwa/src/daemons/notifications.ts +148 -0
  11. package/bin/templates/pwa/src/guards/auth.ts +10 -0
  12. package/bin/templates/pwa/src/main.ts +42 -0
  13. package/bin/templates/pwa/src/middleware/auth.ts +15 -0
  14. package/bin/templates/pwa/src/middleware/error.ts +25 -0
  15. package/bin/templates/pwa/src/middleware/retry.ts +27 -0
  16. package/bin/templates/pwa/src/pages/dashboard.ts +142 -0
  17. package/bin/templates/pwa/src/pages/login.ts +161 -0
  18. package/bin/templates/pwa/src/pages/notifications.ts +156 -0
  19. package/bin/templates/pwa/src/pages/profile.ts +163 -0
  20. package/bin/templates/pwa/src/router.ts +16 -0
  21. package/bin/templates/pwa/src/services/auth.ts +48 -0
  22. package/bin/templates/pwa/src/services/jwt.ts +35 -0
  23. package/bin/templates/pwa/src/services/storage.ts +24 -0
  24. package/bin/templates/pwa/src/styles/global.css +55 -0
  25. package/bin/templates/pwa/src/types/auth.ts +21 -0
  26. package/bin/templates/pwa/src/types/notifications.ts +9 -0
  27. package/bin/templates/pwa/src/utils/fetch.ts +39 -0
  28. package/bin/templates/pwa/tsconfig.json +23 -0
  29. package/bin/templates/pwa/vite.config.ts +94 -0
  30. package/dist/components/music-player/snice-music-player.d.ts +72 -0
  31. package/dist/components/music-player/snice-music-player.js +730 -0
  32. package/dist/components/music-player/snice-music-player.js.map +1 -0
  33. package/dist/components/music-player/snice-music-player.types.d.ts +43 -0
  34. package/dist/components/timer/snice-timer.d.ts +27 -0
  35. package/dist/components/timer/snice-timer.js +197 -0
  36. package/dist/components/timer/snice-timer.js.map +1 -0
  37. package/dist/components/timer/snice-timer.types.d.ts +10 -0
  38. package/dist/fetcher.d.ts +65 -0
  39. package/dist/index.cjs +92 -3
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.esm.js +92 -4
  43. package/dist/index.esm.js.map +1 -1
  44. package/dist/index.iife.js +92 -3
  45. package/dist/index.iife.js.map +1 -1
  46. package/dist/symbols.cjs +1 -1
  47. package/dist/symbols.esm.js +1 -1
  48. package/dist/transitions.cjs +1 -1
  49. package/dist/transitions.esm.js +1 -1
  50. package/dist/types/context.d.ts +7 -1
  51. package/dist/types/router-options.d.ts +6 -0
  52. package/docs/ai/api.md +33 -1
  53. package/docs/ai/components/music-player.md +134 -0
  54. package/docs/ai/components/timer.md +43 -0
  55. package/docs/ai/patterns.md +47 -0
  56. package/docs/components/music-player.md +314 -0
  57. package/docs/components/timer.md +143 -0
  58. package/docs/fetcher.md +447 -0
  59. package/docs/routing.md +2 -0
  60. package/package.json +2 -1
@@ -0,0 +1,35 @@
1
+ interface JWTPayload {
2
+ exp?: number;
3
+ iat?: number;
4
+ sub?: string;
5
+ [key: string]: any;
6
+ }
7
+
8
+ export function decodeJWT(token: string): JWTPayload | null {
9
+ try {
10
+ const parts = token.split('.');
11
+ if (parts.length !== 3) return null;
12
+
13
+ const payload = parts[1];
14
+ const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
15
+ return JSON.parse(decoded);
16
+ } catch (err) {
17
+ console.error('Failed to decode JWT:', err);
18
+ return null;
19
+ }
20
+ }
21
+
22
+ export function isTokenExpired(token: string): boolean {
23
+ const payload = decodeJWT(token);
24
+ if (!payload || !payload.exp) return true;
25
+
26
+ const now = Math.floor(Date.now() / 1000);
27
+ return payload.exp < now;
28
+ }
29
+
30
+ export function getTokenExpiration(token: string): Date | null {
31
+ const payload = decodeJWT(token);
32
+ if (!payload || !payload.exp) return null;
33
+
34
+ return new Date(payload.exp * 1000);
35
+ }
@@ -0,0 +1,24 @@
1
+ const TOKEN_KEY = 'auth_token';
2
+ const USER_KEY = 'auth_user';
3
+
4
+ export function getToken(): string | null {
5
+ return localStorage.getItem(TOKEN_KEY);
6
+ }
7
+
8
+ export function setToken(token: string): void {
9
+ localStorage.setItem(TOKEN_KEY, token);
10
+ }
11
+
12
+ export function clearToken(): void {
13
+ localStorage.removeItem(TOKEN_KEY);
14
+ localStorage.removeItem(USER_KEY);
15
+ }
16
+
17
+ export function getUser<T>(): T | null {
18
+ const user = localStorage.getItem(USER_KEY);
19
+ return user ? JSON.parse(user) : null;
20
+ }
21
+
22
+ export function setUser<T>(user: T): void {
23
+ localStorage.setItem(USER_KEY, JSON.stringify(user));
24
+ }
@@ -0,0 +1,55 @@
1
+ :root {
2
+ --primary-color: #6366f1;
3
+ --secondary-color: #8b5cf6;
4
+ --success-color: #10b981;
5
+ --warning-color: #f59e0b;
6
+ --danger-color: #ef4444;
7
+ --info-color: #3b82f6;
8
+
9
+ --bg-primary: #ffffff;
10
+ --bg-secondary: #f9fafb;
11
+ --text-color: #1f2937;
12
+ --text-light: #6b7280;
13
+ --border-color: #e5e7eb;
14
+
15
+ --radius-sm: 0.375rem;
16
+ --radius-md: 0.5rem;
17
+ --radius-lg: 0.75rem;
18
+
19
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
20
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
21
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
22
+
23
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
24
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
25
+ sans-serif;
26
+ line-height: 1.5;
27
+ font-weight: 400;
28
+ color-scheme: light;
29
+ }
30
+
31
+ * {
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ body {
36
+ margin: 0;
37
+ padding: 0;
38
+ background-color: var(--bg-secondary);
39
+ color: var(--text-color);
40
+ }
41
+
42
+ #app {
43
+ min-height: 100vh;
44
+ }
45
+
46
+ @media (prefers-color-scheme: dark) {
47
+ :root {
48
+ --bg-primary: #1f2937;
49
+ --bg-secondary: #111827;
50
+ --text-color: #f9fafb;
51
+ --text-light: #9ca3af;
52
+ --border-color: #374151;
53
+ color-scheme: dark;
54
+ }
55
+ }
@@ -0,0 +1,21 @@
1
+ export interface User {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ avatar?: string;
6
+ }
7
+
8
+ export interface LoginCredentials {
9
+ email: string;
10
+ password: string;
11
+ }
12
+
13
+ export interface LoginResponse {
14
+ token: string;
15
+ user: User;
16
+ }
17
+
18
+ export interface AppContext {
19
+ user: User | null;
20
+ isAuthenticated: boolean;
21
+ }
@@ -0,0 +1,9 @@
1
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error';
2
+
3
+ export interface Notification {
4
+ id: string;
5
+ title: string;
6
+ message: string;
7
+ type: NotificationType;
8
+ timestamp: string;
9
+ }
@@ -0,0 +1,39 @@
1
+ export type Middleware = (
2
+ url: string,
3
+ options: RequestInit,
4
+ next: () => Promise<Response>
5
+ ) => Promise<Response>;
6
+
7
+ export type FetchConfig = {
8
+ baseURL?: string;
9
+ middleware?: Middleware[];
10
+ headers?: Record<string, string>;
11
+ };
12
+
13
+ export function createFetch(config: FetchConfig = {}) {
14
+ return async (url: string, options: RequestInit = {}): Promise<Response> => {
15
+ const fullURL = config.baseURL ? `${config.baseURL}${url}` : url;
16
+
17
+ const execute = async (): Promise<Response> => {
18
+ return fetch(fullURL, {
19
+ ...options,
20
+ headers: {
21
+ ...config.headers,
22
+ ...options.headers,
23
+ },
24
+ });
25
+ };
26
+
27
+ // Chain middleware (reverse order so first middleware wraps execution)
28
+ let chain = execute;
29
+ if (config.middleware) {
30
+ for (let i = config.middleware.length - 1; i >= 0; i--) {
31
+ const middleware = config.middleware[i];
32
+ const next = chain;
33
+ chain = () => middleware(fullURL, options, next);
34
+ }
35
+ }
36
+
37
+ return chain();
38
+ };
39
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowImportingTsExtensions": true,
9
+ "strict": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "experimentalDecorators": true,
17
+ "useDefineForClassFields": false,
18
+ "noEmit": true,
19
+ "types": ["vite/client"]
20
+ },
21
+ "include": ["src/**/*"],
22
+ "exclude": ["node_modules", "dist"]
23
+ }
@@ -0,0 +1,94 @@
1
+ import { defineConfig } from 'vite';
2
+ import swc from 'unplugin-swc';
3
+ import { VitePWA } from 'vite-plugin-pwa';
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ swc.vite({
8
+ jsc: {
9
+ parser: {
10
+ syntax: 'typescript',
11
+ decorators: true,
12
+ },
13
+ target: 'es2022',
14
+ transform: {
15
+ decoratorMetadata: false,
16
+ decoratorVersion: '2022-03',
17
+ useDefineForClassFields: false,
18
+ },
19
+ },
20
+ }),
21
+ VitePWA({
22
+ registerType: 'autoUpdate',
23
+ includeAssets: ['vite.svg', 'icons/*.png'],
24
+ manifest: {
25
+ name: '{{projectName}}',
26
+ short_name: '{{projectName}}',
27
+ description: 'PWA built with Snice',
28
+ theme_color: '#6366f1',
29
+ background_color: '#ffffff',
30
+ display: 'standalone',
31
+ icons: [
32
+ {
33
+ src: '/icons/icon-192.png',
34
+ sizes: '192x192',
35
+ type: 'image/png',
36
+ },
37
+ {
38
+ src: '/icons/icon-512.png',
39
+ sizes: '512x512',
40
+ type: 'image/png',
41
+ },
42
+ ],
43
+ },
44
+ workbox: {
45
+ globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
46
+ runtimeCaching: [
47
+ {
48
+ urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
49
+ handler: 'CacheFirst',
50
+ options: {
51
+ cacheName: 'google-fonts-cache',
52
+ expiration: {
53
+ maxEntries: 10,
54
+ maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
55
+ },
56
+ cacheableResponse: {
57
+ statuses: [0, 200],
58
+ },
59
+ },
60
+ },
61
+ {
62
+ urlPattern: /^https:\/\/api\..*/i,
63
+ handler: 'NetworkFirst',
64
+ options: {
65
+ cacheName: 'api-cache',
66
+ networkTimeoutSeconds: 10,
67
+ expiration: {
68
+ maxEntries: 50,
69
+ maxAgeSeconds: 60 * 5, // 5 minutes
70
+ },
71
+ },
72
+ },
73
+ ],
74
+ },
75
+ }),
76
+ ],
77
+ build: {
78
+ target: 'es2015',
79
+ minify: 'terser',
80
+ cssMinify: true,
81
+ rollupOptions: {
82
+ output: {
83
+ manualChunks: {
84
+ vendor: ['snice'],
85
+ },
86
+ },
87
+ },
88
+ sourcemap: true,
89
+ chunkSizeWarningLimit: 500,
90
+ },
91
+ esbuild: {
92
+ drop: process.env.NODE_ENV === 'production' ? ['debugger'] : [],
93
+ },
94
+ });
@@ -0,0 +1,72 @@
1
+ import type { Track, RepeatMode, PlayerState, SniceMusicPlayerElement } from './snice-music-player.types';
2
+ export declare class SniceMusicPlayer extends HTMLElement implements SniceMusicPlayerElement {
3
+ tracks: Track[];
4
+ currentTrackIndex: number;
5
+ currentTrack: string;
6
+ currentTime: number;
7
+ duration: number;
8
+ volume: number;
9
+ muted: boolean;
10
+ shuffle: boolean;
11
+ repeat: RepeatMode;
12
+ state: PlayerState;
13
+ autoplay: boolean;
14
+ showPlaylist: boolean;
15
+ showControls: boolean;
16
+ showVolume: boolean;
17
+ showArtwork: boolean;
18
+ showTrackInfo: boolean;
19
+ compact: boolean;
20
+ private showVolumeSlider;
21
+ private audioElement;
22
+ private updateInterval;
23
+ private shuffleHistory;
24
+ private shuffleQueue;
25
+ private progressElement?;
26
+ private volumeSlider?;
27
+ componentStyles(): import("snice").CSSResult;
28
+ init(): void;
29
+ cleanup(): void;
30
+ handleTracksChange(): void;
31
+ handleCurrentTrackChange(oldVal: string, newVal: string): void;
32
+ handleVolumeChange(oldVal: number, newVal: number): void;
33
+ handleMutedChange(oldVal: boolean, newVal: boolean): void;
34
+ renderPlayer(): import("snice").TemplateResult;
35
+ play(): Promise<void>;
36
+ pause(): void;
37
+ stop(): void;
38
+ next(): void;
39
+ previous(): void;
40
+ seek(time: number): void;
41
+ setVolume(volume: number): void;
42
+ toggleShuffle(): void;
43
+ setRepeat(mode: RepeatMode): void;
44
+ loadTrack(index: number): Promise<void>;
45
+ getCurrentTrack(): Track | null;
46
+ private toggleMute;
47
+ private toggleVolumeSlider;
48
+ private cycleRepeat;
49
+ private handleSeek;
50
+ private handleVolumeSlider;
51
+ private handleTrackEnded;
52
+ private getNextShuffleIndex;
53
+ private initializeShuffleQueue;
54
+ private startUpdateInterval;
55
+ private stopUpdateInterval;
56
+ private formatTime;
57
+ private emitPlay;
58
+ private emitPause;
59
+ private emitStop;
60
+ private emitTrackChange;
61
+ private emitTrackEnded;
62
+ private emitSeek;
63
+ private emitVolumeChange;
64
+ private emitShuffleChange;
65
+ private emitRepeatChange;
66
+ private emitError;
67
+ }
68
+ declare global {
69
+ interface HTMLElementTagNameMap {
70
+ 'snice-music-player': SniceMusicPlayer;
71
+ }
72
+ }