JavaScript Conventions
Estudo Organizado uses Vanilla JavaScript (ES6+) without frameworks. This guide documents the coding patterns observed throughout the codebase.
Module Structure
Export Pattern
Use named exports for all functions and constants:
// ✅ Good - Named exports
export const DB_NAME = 'EstudoOrganizadoDB' ;
export const DB_VERSION = 1 ;
export function initDB () {
// implementation
}
export function setState ( newState ) {
// implementation
}
// ❌ Avoid - Default exports
export default function initDB () {
// ...
}
Import Pattern
Import only what you need using destructuring:
// ✅ Good - Specific imports
import { pushToCloudflare } from './cloud-sync.js' ;
import { uid } from './utils.js' ;
Always include the .js extension in import statements for compatibility with native ES modules.
File Organization
Start each module with a descriptive header:
// =============================================
// SCHEMA & STATE MANAGEMENT (INDEXEDDB)
// =============================================
Use this pattern for major functional sections within files as well.
Logical Grouping
Group related functions together and separate them with comments:
// Initialize DB and load state
export function initDB () {
// ...
}
export function loadStateFromDB () {
// ...
}
// Fallback to load from LocalStorage
export function loadLegacyState () {
// ...
}
Naming Conventions
Constants
Use SCREAMING_SNAKE_CASE for module-level constants:
export const DB_NAME = 'EstudoOrganizadoDB' ;
export const DB_VERSION = 1 ;
export const STORE_NAME = 'app_state' ;
export const DEFAULT_SCHEMA_VERSION = 7 ;
Variables
Use camelCase for variables and function names:
export let db ;
export let saveTimeout = null ;
const isSameSession = sessionStorage . getItem ( 'estudo_session_active' );
const loadedState = request . result ;
Functions
Use descriptive verb-based names in camelCase:
export function setState ( newState ) { }
export function scheduleSave () { }
export function saveStateToDB () { }
export function runMigrations () { }
export function clearData () { }
Object Properties
Use camelCase for object properties:
const state = {
schemaVersion: DEFAULT_SCHEMA_VERSION ,
ciclo: { ativo: false , ciclosCompletos: 0 , disciplinas: [] },
planejamento: { ativo: false , tipo: null },
editais: [],
eventos: [],
habitos: { questoes: [], revisao: [] }
};
Portuguese property names are used throughout the project to match the domain language. Maintain this convention for consistency.
Code Patterns
State Management
Normalizing State
Always provide default values when setting state:
export function setState ( newState ) {
const normalized = {
schemaVersion: newState . schemaVersion || DEFAULT_SCHEMA_VERSION ,
ciclo: newState . ciclo || { ativo: false , ciclosCompletos: 0 , disciplinas: [] },
editais: newState . editais || [],
eventos: newState . eventos || [],
config: Object . assign ({
visualizacao: 'mes' ,
primeirodiaSemana: 1
}, newState . config || {})
};
Object . keys ( state ). forEach ( k => delete state [ k ]);
Object . assign ( state , normalized );
}
Key points:
Use || operator for simple defaults
Use Object.assign() for merging with default objects
Replace state properties without changing reference
Promises
Return Promises for asynchronous operations:
export function initDB () {
return new Promise (( resolve , reject ) => {
const request = indexedDB . open ( DB_NAME , DB_VERSION );
request . onerror = ( event ) => {
console . error ( 'IndexedDB Error:' , event . target . error );
loadLegacyState ();
resolve (); // Resolve even on error with fallback
};
request . onsuccess = ( event ) => {
db = event . target . result ;
loadStateFromDB (). then (() => resolve ());
};
});
}
Event Handling
Use custom events for cross-module communication:
// Dispatching events
document . dispatchEvent ( new Event ( 'app:invalidateCaches' ));
document . dispatchEvent ( new Event ( 'app:updateBadges' ));
document . dispatchEvent ( new Event ( 'stateSaved' ));
// With data
document . dispatchEvent ( new CustomEvent ( 'app:showToast' , {
detail: { msg: 'Dados apagados com sucesso.' , type: 'info' }
}));
Error Handling
Always handle errors gracefully with fallbacks:
request . onerror = () => {
loadLegacyState (); // Fallback mechanism
resolve ();
};
try {
const saved = localStorage . getItem ( 'estudo_state' );
if ( saved ) {
setState ( JSON . parse ( saved ));
}
} catch ( e ) {
console . error ( 'Error loading legacy state:' , e );
}
DO comment:
Complex business logic
Bug fixes with context
Migration steps
Non-obvious workarounds
// BUG 3: Prevenir persistência inflada de timer ao fechar a aba
const isSameSession = sessionStorage . getItem ( 'estudo_session_active' );
if ( ! isSameSession ) {
if ( loadedState . cronoLivre && loadedState . cronoLivre . _timerStart ) {
loadedState . cronoLivre . _timerStart = null ;
}
}
// Wave 39: Separation between Assuntos (Edital Topics) and Aulas (Course Materials)
if ( state . schemaVersion < 7 ) {
// Migration logic
}
DON’T comment:
Obvious code
What the code does (code should be self-documenting)
// ❌ Bad - Obvious
// Loop through editais
state . editais . forEach ( ed => { });
// ✅ Good - Self-documenting
state . editais . forEach ( edital => {
edital . disciplinas . forEach ( disciplina => {
// Process each subject
});
});
Document migrations with context:
if ( state . schemaVersion < 2 ) {
// Add IDs where missing
state . editais . forEach ( ed => {
if ( ! ed . id ) ed . id = 'ed_' + uid ();
if ( ! ed . cor ) ed . cor = '#10b981' ;
// Migration: flatten grupos into disciplinas
if ( ed . grupos && ! ed . disciplinas ) {
ed . disciplinas = [];
ed . grupos . forEach ( gr => {
gr . disciplinas . forEach ( d => ed . disciplinas . push ( d ));
});
delete ed . grupos ;
}
});
state . schemaVersion = 2 ;
changed = true ;
}
Async Patterns
Queue Pattern
For sequential async operations:
export const SyncQueue = {
isProcessing: false ,
tasks: [],
add ( taskFn ) {
return new Promise (( resolve , reject ) => {
this . tasks . push ( async () => {
try {
await taskFn ();
resolve ();
} catch ( err ) {
reject ( err );
}
});
this . process ();
});
},
async process () {
if ( this . isProcessing ) return ;
this . isProcessing = true ;
while ( this . tasks . length > 0 ) {
const fn = this . tasks . shift ();
try {
await fn ();
} catch ( err ) {
console . error ( 'SyncQueue Error:' , err );
}
}
this . isProcessing = false ;
}
};
Debouncing
Use debouncing for frequent operations:
export function scheduleSave () {
if ( saveTimeout ) clearTimeout ( saveTimeout );
// Update UI instantly
document . dispatchEvent ( new Event ( 'app:updateBadges' ));
// Save after debounce period
saveTimeout = setTimeout (() => {
saveStateToDB ();
}, 2000 ); // 2 second debounce
}
Data Initialization
Default Values
Provide comprehensive defaults:
export let state = {
schemaVersion: DEFAULT_SCHEMA_VERSION ,
ciclo: { ativo: false , ciclosCompletos: 0 , disciplinas: [] },
eventos: [],
habitos: {
questoes: [],
revisao: [],
discursiva: [],
simulado: []
},
config: {
visualizacao: 'mes' ,
primeirodiaSemana: 1 ,
frequenciaRevisao: [ 1 , 7 , 30 , 90 ]
}
};
Backward Compatibility
Ensure new properties work with old data:
// Normalize habitos keys
if ( state . habitos ) {
if ( state . habitos . sumulas && ! state . habitos . sumula ) {
state . habitos . sumula = state . habitos . sumulas ;
delete state . habitos . sumulas ;
}
if ( ! state . habitos . videoaula ) state . habitos . videoaula = [];
}
Regex Patterns
Document regex purposes:
// Match lesson-like topic names
const classRegex = / ( ^ aula \s * \d + ) | ( ^ modulo \s * \d + ) / i ;
if ( classRegex . test ( ass . nome . trim ())) {
// Handle as course lesson
}
✅ Use named exports
✅ Include .js in imports
✅ SCREAMING_SNAKE_CASE for constants
✅ camelCase for variables and functions
✅ Descriptive function names with verbs
✅ Provide default values in state
✅ Return Promises for async operations
✅ Handle errors with fallbacks
✅ Comment complex logic and bugs
✅ Use custom events for cross-module communication
✅ Document migrations with context
✅ Debounce frequent operations
✅ Maintain Portuguese domain terminology
Best Practices Summary
Keep it simple : Vanilla JavaScript, no unnecessary abstractions
Performance first : Debounce saves, cache when possible
Fail gracefully : Always provide fallbacks
Document intent : Comment the “why”, not the “what”
Maintain compatibility : Support data migrations
Test thoroughly : See Testing Guide