Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .github/workflows/release-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,33 @@ jobs:
cache: "npm"
cache-dependency-path: extension/package-lock.json

- name: Get version
id: version
run: echo "version=$(node -p "require('./extension/package.json').version")" >> $GITHUB_OUTPUT

- name: Build extension
working-directory: extension
run: npm ci && npm run build

- name: Package extension
working-directory: extension
shell: pwsh
run: Compress-Archive -Path dist/* -DestinationPath worktrace-extension.zip -Force
run: zip -r "worktrace-extension-v${{ steps.version.outputs.version }}.zip" dist/

- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "extension-build-${{ github.run_number }}" \
extension/worktrace-extension.zip \
--title "WorkTrace Extension — build ${{ github.run_number }}" \
VERSION="v${{ steps.version.outputs.version }}"
if gh release view "$VERSION" &>/dev/null; then
echo "Release $VERSION already exists — skipping."
exit 0
fi
gh release create "$VERSION" \
"extension/worktrace-extension-$VERSION.zip" \
--title "WorkTrace Extension $VERSION" \
--notes "Автоматичний реліз при мерджі в main.

Щоб завантажити розширення:
1. Скачай \`worktrace-extension.zip\`
1. Скачай \`worktrace-extension-$VERSION.zip\`
2. Розпакуй у будь-яку папку
3. chrome://extensions → Developer mode → Load unpacked → обери розпаковану папку"
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ and generating AI session reports.
## Auth
Auth logic lives ONLY in:
- `dashboard/app/api/auth/**/route.ts` — Google token verification, JWT issuance
- `dashboard/middleware.ts` — route guarding for `/dashboard/*`
- `dashboard/proxy.ts` — route guarding for `/dashboard/*`

Do NOT duplicate auth checks in components, server modules, or other Route Handlers.

Expand Down
9 changes: 7 additions & 2 deletions dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# ─── Database ──────────────────────────────────────────────────────────────────
# Local dev: credentials from docker-compose.yml (port 5433).
# Neon (prod): https://console.neon.tech → your project → Connection string
# Use the "Pooled connection" string for DATABASE_URL.
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/worktrace"

# Neon (prod): https://console.neon.tech → your project → Connection string
# Use the "Pooled connection" string for DATABASE_URL and direct for DIRECT_URL.
# Use sslmode=verify-full (not require) to keep certificate verification and silence
# the pg-connection-string deprecation warning.
# DIRECT_URL="postgresql://<user>:<pass>@<host>.neon.tech/<db>?sslmode=verify-full"
# DATABASE_URL="postgresql://<user>:<pass>@<host>-pooler.neon.tech/<db>?sslmode=verify-full&channel_binding=require"

# ─── Google OAuth ──────────────────────────────────────────────────────────────
# https://console.cloud.google.com → APIs & Services → Credentials
# → "+ CREATE CREDENTIALS" → "OAuth 2.0 Client ID"
Expand Down
5 changes: 3 additions & 2 deletions dashboard/app/dashboard/charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ export function Charts({ filters }: { filters: FeedFilters }) {
const { data, isLoading, isError } = useQuery({
queryKey: statsQueryKey(filters),
queryFn: () => fetchEventStats(filters),
placeholderData: keepPreviousData,
staleTime: 30_000,
placeholderData: keepPreviousData,
staleTime: 30_000,
refetchInterval: 30_000,
});

if (isLoading) {
Expand Down
41 changes: 31 additions & 10 deletions dashboard/app/dashboard/event-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ function hostnameOf(url: string): string {
}
}

function isHttpUrl(url: string): boolean {
return url.startsWith("http://") || url.startsWith("https://");
}

function utcShort(iso: string): string {
return iso.slice(0, 16).replace("T", " ") + " UTC";
}
Expand All @@ -36,15 +40,21 @@ export function EventCard({ event }: { event: EventDTO }) {
whileHover={{ scale: 1.012, transition: { duration: 0.15 } }}
>
<div className="flex items-baseline justify-between gap-3">
<a
href={event.url}
target="_blank"
rel="noreferrer noopener"
className="cursor-pointer truncate text-sm font-bold text-text transition-colors hover:text-cyan"
title={event.title}
>
{event.title}
</a>
{isHttpUrl(event.url) ? (
<a
href={event.url}
target="_blank"
rel="noreferrer noopener"
className="cursor-pointer truncate text-sm font-bold text-text transition-colors hover:text-cyan"
title={event.title}
>
{event.title}
</a>
) : (
<span className="truncate text-sm font-bold text-text" title={event.title}>
{event.title}
</span>
)}
<time
className="shrink-0 text-[10px] text-muted"
dateTime={event.timestamp}
Expand All @@ -55,7 +65,18 @@ export function EventCard({ event }: { event: EventDTO }) {

<div className="flex items-center gap-2 text-[11px] text-muted">
<span className="text-purple">⟶</span>
<span className="truncate">{hostnameOf(event.url)}</span>
{isHttpUrl(event.url) ? (
<a
href={event.url}
target="_blank"
rel="noreferrer noopener"
className="truncate transition-colors hover:text-cyan"
>
{hostnameOf(event.url)}
</a>
) : (
<span className="truncate">{hostnameOf(event.url)}</span>
)}
</div>

{event.content ? (
Expand Down
1 change: 1 addition & 0 deletions dashboard/app/dashboard/event-feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function EventFeed({ filters, onClearFilters }: Props) {
getNextPageParam: (last) => last.nextCursor,
placeholderData: keepPreviousData,
staleTime: 30_000,
refetchInterval: 30_000,
});

const events = useMemo(
Expand Down
5 changes: 3 additions & 2 deletions dashboard/app/dashboard/music/music-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ export function MusicClient() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ["music-stats", range],
queryFn: () => fetchMusicStats(range),
placeholderData: keepPreviousData,
staleTime: 60_000,
placeholderData: keepPreviousData,
staleTime: 60_000,
refetchInterval: 60_000,
});

return (
Expand Down
7 changes: 4 additions & 3 deletions dashboard/app/dashboard/top-sessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ function localDay(iso: string): string {

export function TopSessions() {
const { data, isLoading, isError } = useQuery({
queryKey: topSessionsQueryKey,
queryFn: fetchTopSessions,
staleTime: 30_000,
queryKey: topSessionsQueryKey,
queryFn: fetchTopSessions,
staleTime: 30_000,
refetchInterval: 30_000,
});

if (isLoading) {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/middleware.ts → dashboard/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const config = {
matcher: ["/dashboard/:path*", "/login"],
};

export async function middleware(req: NextRequest) {
export async function proxy(req: NextRequest) {
const token = req.cookies.get(SESSION_COOKIE)?.value;
const isAuth = token ? await isValid(token) : false;

Expand Down
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "worktrace-extension",
"version": "0.1.0",
"version": "1.0.2",
"private": true,
"type": "module",
"description": "WorkTrace Chrome extension — captures dev session context.",
Expand Down
Loading