Skip to content

Commit 82c3a94

Browse files
committed
Revisa trilha LLM do Zero e corrige bug de progressão
1 parent 4c21cb8 commit 82c3a94

20 files changed

+422
-181
lines changed

gaeia-web/CLAUDE.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55
**IMPORTANT: Always use Docker for all build, dev, and test commands.**
66

7+
## Tech Stack
8+
9+
- **Astro 5** (SSR with `@astrojs/node` or static for GitHub Pages)
10+
- **Tailwind CSS v4** (via `@tailwindcss/vite` plugin, not PostCSS)
11+
- **Shiki** for syntax highlighting (custom neural theme in `src/utils/shiki-neural-theme.ts`)
12+
- **Marked** for markdown rendering
13+
- **TypeScript** (strict mode, `astro/tsconfigs/strict`)
14+
- No test framework configured
15+
716
```bash
817
# Build the project
918
docker compose run --rm gaeia-web npm run build
@@ -20,10 +29,21 @@ docker compose run --rm gaeia-web npm install
2029

2130
## Project Structure
2231

32+
### Path Aliases (tsconfig.json)
33+
34+
- `@/*``src/*`
35+
- `@components/*``src/components/*`
36+
- `@layouts/*``src/layouts/*`
37+
- `@utils/*``src/utils/*`
38+
2339
- `src/components/` - Astro components
2440
- `src/pages/` - Astro pages (file-based routing)
2541
- `src/utils/` - Utility functions
2642
- `src/types/` - TypeScript type definitions
43+
- `src/scripts/` - Client-side scripts (progress hydration, interactive checklist)
44+
- `src/styles/` - Global CSS (`global.css`, `markdown.css`, `particles.css`)
45+
- `src/layouts/` - Base layout (single `BaseLayout.astro`)
46+
- `docker/` - Dockerfile.dev, Dockerfile.prod, nginx.conf
2747

2848
## Content Architecture
2949

@@ -102,3 +122,15 @@ The `universe/_catalog.json` uses direct JSON file paths:
102122
"topicos": { "path": "topicos", "indexFile": "_index.json" }
103123
}
104124
```
125+
126+
## Output Modes
127+
128+
Controlled by `PUBLIC_MODE` env var in `astro.config.mjs`:
129+
- `PUBLIC_MODE=hybrid` (default, Docker) → SSR with node adapter, `base: '/'`
130+
- `PUBLIC_MODE=static` → Static build for GitHub Pages, `base: '/gaeia'`
131+
132+
## Language
133+
134+
- UI and content are in **Brazilian Portuguese** (pt-BR)
135+
- Variable/function names use Portuguese terms (trilha, topico, modulo, conquistas)
136+
- Code comments and logs are in English

gaeia-web/src/pages/index.astro

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import BaseLayout from '../layouts/BaseLayout.astro';
33
import ProgressBar from '../components/ProgressBar.astro';
44
import StreakCounter from '../components/StreakCounter.astro';
55
import BadgeShelf from '../components/BadgeShelf.astro';
6-
import { getAllTrilhas, getTrilhaTopicoIds, getAllTopicos, getAllTags } from '../utils/trilhas';
6+
import { getAllTrilhas, getTrilhaTopicoIds, getAllTopicos, getAllTags, computeHorasEstimadas } from '../utils/trilhas';
77
import { calculateEarnedBadges } from '../utils/badges';
88
import { TRILHA_ICON_PATHS } from '../utils/uiConstants';
99
import { url } from '../utils/url';
1010
1111
// Load trilhas and topics
1212
const trilhas = await getAllTrilhas();
13+
const trilhasHoras = await Promise.all(
14+
trilhas.map(async (trilha) => ({
15+
trilha,
16+
horas: await computeHorasEstimadas(trilha.id),
17+
}))
18+
);
19+
const horasMap = new Map(trilhasHoras.map(({ trilha, horas }) => [trilha.id, horas]));
1320
const topicos = await getAllTopicos();
1421
const topicosCount = topicos.length;
1522
const allTags = await getAllTags();
@@ -66,7 +73,7 @@ const badges = calculateEarnedBadges();
6673
</p>
6774
<div class="flex items-center gap-4 text-sm text-dim">
6875
<span>{totalTopicos} topicos</span>
69-
<span>~{trilha.horasEstimadas}h</span>
76+
<span>~{horasMap.get(trilha.id)}h</span>
7077
</div>
7178

7279
<!-- Progress placeholder -->

gaeia-web/src/pages/trilhas/index.astro

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
---
22
import BaseLayout from '../../layouts/BaseLayout.astro';
33
import ProgressBar from '../../components/ProgressBar.astro';
4-
import { getAllTrilhas, getTrilhaTopicoIds } from '../../utils/trilhas';
4+
import { getAllTrilhas, getTrilhaTopicoIds, computeHorasEstimadas } from '../../utils/trilhas';
55
import { TRILHA_ICON_PATHS, NIVEL_LABELS, NIVEL_COLORS_TEXT } from '../../utils/uiConstants';
66
import { url } from '../../utils/url';
77
8-
// Load all trilhas
8+
// Load all trilhas and pre-compute hours from topics
99
const trilhas = await getAllTrilhas();
10+
const trilhasHoras = await Promise.all(
11+
trilhas.map(async (trilha) => ({
12+
trilha,
13+
horas: await computeHorasEstimadas(trilha.id),
14+
}))
15+
);
16+
const horasMap = new Map(trilhasHoras.map(({ trilha, horas }) => [trilha.id, horas]));
1017
---
1118

1219
<BaseLayout title="Trilhas de Aprendizado">
@@ -59,7 +66,7 @@ const trilhas = await getAllTrilhas();
5966
{totalTopicos} topicos
6067
</span>
6168
<span class="text-dim">
62-
~{trilha.horasEstimadas}h
69+
~{horasMap.get(trilha.id)}h
6370
</span>
6471
</div>
6572

@@ -108,7 +115,7 @@ const trilhas = await getAllTrilhas();
108115
<td class="py-3 px-4 font-medium text-bright">{trilha.nome}</td>
109116
<td class={`py-3 px-4 ${NIVEL_COLORS_TEXT[trilha.nivel]}`}>{NIVEL_LABELS[trilha.nivel]}</td>
110117
<td class="py-3 px-4 text-dim">{getTrilhaTopicoIds(trilha).length}</td>
111-
<td class="py-3 px-4 text-dim">~{trilha.horasEstimadas}h</td>
118+
<td class="py-3 px-4 text-dim">~{horasMap.get(trilha.id)}h</td>
112119
<td class="py-3 px-4 text-dim line-clamp-1">{trilha.descricao}</td>
113120
</tr>
114121
))}

gaeia-web/src/scripts/interactive-checklist.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Also hydrates inline checklist containers from markdown
55
*/
66

7-
import { recordActivity, updateTopicoChecklist, getTopicoProgress } from '../utils/progressStore';
7+
import { updateTopicoChecklist, getTopicoProgress } from '../utils/progressStore';
88

99
export interface ChecklistConfig {
1010
container: HTMLElement;
@@ -122,23 +122,20 @@ async function toggleCheckbox(
122122
const data = await response.json();
123123
throw new Error(data.error || 'Sync failed');
124124
}
125-
} else {
126-
// Modo static: salvar diretamente no localStorage
127-
const progress = getTopicoProgress(blockSlug || '') || { checklist: [] as boolean[], completo: false };
128-
const checklist = [...progress.checklist];
125+
}
129126

130-
while (checklist.length <= index) {
131-
checklist.push(false);
132-
}
133-
checklist[index] = newChecked;
127+
// Salvar no localStorage (ambos os modos)
128+
const progress = getTopicoProgress(blockSlug || '') || { checklist: [] as boolean[], completo: false };
129+
const checklist = [...progress.checklist];
134130

135-
updateTopicoChecklist(blockSlug || '', checklist);
131+
// Garantir que o array tenha o tamanho correto (total de itens do checklist no DOM)
132+
const totalItems = container.querySelectorAll('.checklist-item').length;
133+
while (checklist.length < totalItems) {
134+
checklist.push(false);
136135
}
136+
checklist[index] = newChecked;
137137

138-
// Registrar atividade para streaks (ambos os modos)
139-
if (newChecked) {
140-
recordActivity();
141-
}
138+
updateTopicoChecklist(blockSlug || '', checklist);
142139

143140
// Disparar evento para outros componentes
144141
window.dispatchEvent(new CustomEvent('progress-updated', {

gaeia-web/src/scripts/progress-hydrator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function hydrateTrilhaProgress(): void {
7979
if (text.includes('%')) {
8080
textEl.textContent = `${percentage}%`;
8181
} else if (text.includes('/') && text.includes('topicos')) {
82-
textEl.textContent = `${completedCount}/${totalCount} topicos`;
82+
textEl.textContent = `${Math.round(completedCount)}/${totalCount} topicos`;
8383
}
8484
});
8585
});
@@ -141,7 +141,8 @@ function hydrateSidebarProgress(): void {
141141
// Update text below the ring
142142
const progressText = document.getElementById('sidebar-progress-text');
143143
if (progressText) {
144-
progressText.textContent = `${completedCount} topicos completos`;
144+
const displayPct = totalTopicos > 0 ? Math.round((completedCount / totalTopicos) * 100) : 0;
145+
progressText.textContent = `${displayPct}% concluído`;
145146
}
146147
}
147148

gaeia-web/src/types/trilhas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export interface Trilha {
142142
descricao: string;
143143
icone: string;
144144
nivel: 'iniciante' | 'intermediario' | 'avancado';
145-
horasEstimadas: number;
145+
horasEstimadas?: number;
146146
modulos: TrilhaModulo[];
147147
badges: TrilhaBadge[];
148148
}

gaeia-web/src/utils/progressStore.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,15 @@ export function isTopicoComplete(topicoId: string): boolean {
218218
*/
219219
export function getCompletedTopicosCount(): number {
220220
const allProgress = getTopicosProgress();
221-
return Object.values(allProgress).filter(p => p.completo).length;
221+
let count = 0;
222+
for (const p of Object.values(allProgress)) {
223+
if (p.completo) {
224+
count += 1;
225+
} else if (p.checklist.length > 0) {
226+
count += p.checklist.filter(Boolean).length / p.checklist.length;
227+
}
228+
}
229+
return count;
222230
}
223231

224232
/**
@@ -245,18 +253,24 @@ export function calculateTrilhaProgress(topicoIds: string[]): {
245253
}
246254

247255
const allProgress = getTopicosProgress();
248-
let completedCount = 0;
256+
let fractionalCompleted = 0;
249257

250258
for (const id of topicoIds) {
251-
if (allProgress[id]?.completo) {
252-
completedCount++;
259+
const progress = allProgress[id];
260+
if (!progress) continue;
261+
262+
if (progress.completo) {
263+
fractionalCompleted += 1;
264+
} else if (progress.checklist.length > 0) {
265+
const checked = progress.checklist.filter(Boolean).length;
266+
fractionalCompleted += checked / progress.checklist.length;
253267
}
254268
}
255269

256270
return {
257-
completedCount,
271+
completedCount: fractionalCompleted,
258272
totalCount: topicoIds.length,
259-
percentage: Math.round((completedCount / topicoIds.length) * 100),
273+
percentage: Math.round((fractionalCompleted / topicoIds.length) * 100),
260274
};
261275
}
262276

gaeia-web/src/utils/trilhas.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,15 @@ export async function getTopicoTrilhas(topicoId: string): Promise<TopicoTrilhas>
431431
// Utility Functions
432432
// ============================================
433433

434+
/**
435+
* Compute total estimated hours for a trilha from its topics' tempoEstimado
436+
*/
437+
export async function computeHorasEstimadas(trilhaId: string): Promise<number> {
438+
const topicos = await getTrilhaTopicos(trilhaId);
439+
const totalMinutos = topicos.reduce((sum, t) => sum + t.tempoEstimado, 0);
440+
return Math.round(totalMinutos / 60);
441+
}
442+
434443
/**
435444
* Get all unique tags from all topics
436445
*/

0 commit comments

Comments
 (0)