v0.1.1 88fcae1

+layout.svelte RawHistoryFolder

1<script lang="ts">
2	import '../app.css';;
3	import { chatState } from '$lib/states/chat.svelte';
4	import { onMount, onDestroy } from 'svelte';
5	import { browser } from '$app/environment';
6
7	let { children, data } = $props();
8	let isVisible = $state(true);
9	let statusInterval = $state<number | undefined>(undefined);
10
11	async function updateUserStatus(status: 'online' | 'offline' | 'busy') {
12		if (!data.user?.id) return;
13		
14		const timestamp = new Date().toISOString();
15		console.debug('Updating user status:', {
16			userId: data.user.id,
17			status,
18			timestamp,
19			trigger: new Error().stack?.split('\n')[2]?.trim()
20		});
21
22		try {
23			const response = await fetch('/api/status', {
24				method: 'POST',
25				headers: { 'Content-Type': 'application/json' },
26				body: JSON.stringify({ 
27					status,
28					lastSeen: timestamp
29				})
30			});
31			
32			if (!response.ok) {
33				console.error('Failed to update user status:', await response.json());
34			}
35		} catch (error) {
36			console.error('Error updating user status:', error);
37		}
38	}
39
40	function startStatusTicker() {
41		if (statusInterval) return;
42		
43		console.debug('Starting status ticker');
44		const tickHandler = () => {
45			if (isVisible) {
46				updateUserStatus('online');
47			}
48		};
49
50		statusInterval = window.setInterval(tickHandler, 60000); // Every minute
51		// Run immediately on start
52		tickHandler();
53	}
54
55	function stopStatusTicker() {
56		if (!statusInterval) return;
57		console.debug('Stopping status ticker');
58		window.clearInterval(statusInterval);
59		statusInterval = undefined;
60	}
61
62	function handleVisibilityChange() {
63		if (!browser) return;
64		isVisible = document.visibilityState === 'visible';
65		updateUserStatus(isVisible ? 'online' : 'busy');
66		
67		// Start or stop the ticker based on visibility
68		if (isVisible) {
69			startStatusTicker();
70		} else {
71			stopStatusTicker();
72		}
73	}
74
75	function cleanup() {
76		if (!browser) return;
77		console.debug('Running cleanup - Setting user status to offline');
78		document.removeEventListener('visibilitychange', handleVisibilityChange);
79		stopStatusTicker();
80		updateUserStatus('offline');
81	}
82
83	onMount(() => {
84		if (!browser) return;
85
86		// Set initial online status
87		updateUserStatus('online');
88
89		// Start the status ticker
90		startStatusTicker();
91
92		// Add visibility change listener
93		document.addEventListener('visibilitychange', handleVisibilityChange);
94
95		// Add beforeunload listener
96		window.addEventListener('beforeunload', cleanup);
97
98		// Cleanup on unmount
99		return () => {
100			window.removeEventListener('beforeunload', cleanup);
101			cleanup();
102		};
103	});
104
105	// Additional safety net for clean navigation within the app
106	onDestroy(() => {
107		if (!browser) return;
108		console.debug('Component being destroyed - Running cleanup');
109		window.removeEventListener('beforeunload', cleanup);
110		cleanup();
111	});
112
113	$effect(() => {
114		if (!browser) return;
115		console.debug('Layout reactive update - Session state:', {
116			timestamp: new Date().toISOString(),
117			hasUser: !!data.user,
118			userDetails: data.user ? {
119				nickname: data.user.nickname,
120				status: data.user.status,
121				id: data.user.id
122			} : null,
123			hasSession: !!data.session,
124			sessionDetails: data.session ? {
125				id: data.session.id,
126				expiresAt: new Date(data.session.expiresAt)
127			} : null,
128			cookiePresent: document.cookie.includes('session='),
129			isVisible
130		});
131	});
132
133	$effect(() => {
134		console.debug('Updating global chatState with data.user:', data.user);
135		chatState.setCurrentUser(data.user);
136	});
137</script>
138
139<div class="windows-xp-bg">
140	{@render children()}
141</div>
142
143<style>
144	.windows-xp-bg {
145		position: fixed;
146		top: 0;
147		left: 0;
148		width: 100vw;
149		height: 100vh;
150		margin: 0;
151		padding: 0;
152		overflow: hidden;
153		background: linear-gradient(
154			to bottom,
155			#2195f2 0%,    /* Lighter blue at the top */
156			#4aa6e2 25%,   /* Mid-light blue */
157			#7ab8e0 50%,   /* Mid blue */
158			#86c55c 75%,   /* Blue-green transition */
159			#90d047 100%   /* Bright green at the bottom */
160		);
161	}
162</style>

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close