Appearance
Appearance
The following patterns will keep the frontend code maintainable long-term.
We organize files by their domain (the feature), not their technical role (the file type or technology).
Rule: Group modules according to the feature they belong to.
Adding, removing, or evolving one feature should only affect one slice of the app. Changing one feature must not affect another. If a contributor wants to fix the Audio Player, they should only need to look in ~/audio and ~/layout, rather than hunting through global components, stores, and views folders.
See Issue #2522 refactor(front): Simplify routes for better DX
Components, functions and code blocks should have precise and meaningful interfaces (inputs/outputs).
Rule: Place complex, self-contained logic into a single module, but keep its interface (props, emitted events, exports) very strict and narrow. Implement exactly the behavior one would expect when reading the interface.
You only need to read a module's interface to understand how to use it. Long and complex modules and are fine if they behave according to the simple and explicit API. Complex logic inside a template is fine if it is what you'd expect when reading the interface. Long modules are fine as long as the scope is covered by the interface.
Example: The Tokens component routes all events through a single state machine with clearly defined states and transitions, making it easier to debug despite its complexity. The interface (props, model, imports) remains small and declarative.
Rule: Keep variables, constants, and closures physically close to where they are used.
When variables have limited scope, we don't need to keep pointers in mind while parsing modules. Local and short relations are preferable over circuitous dependencies.
Rule: Always use the least powerful tool necessary to achieve the outcome.
Less powerful tools have fewer side effects and are easier to parse.
Data flows should be unidirectional, direct, and explicit to avoid situations where two sources of truth compete
Rule: Parse data early at the boundaries (API/Props) and give it the tightest possible shape.
Rule: Cancel unneeded code paths as early as possible (via early returns or defaulting), but compute derived values as late as possible.
Avoids a soup of intermediate, dangling variables in your script.
Example: Default props using the destructuring pattern. If you need a derivative of a prop, instead of using computed in the script, write the formula directly in the template.
TypeScript and Vue bugs in defineProps
boolean | undefined into boolean. As a workaround, use 'true' | 'false' | undefined....rest with defineProps when the module is parametrized over a generic. Rule: Treat complex logic as a state machine with named states and transitions (events).
It prevents impossible UI states (like isLoading while having an error simultaneously), infinite loops and dead code. And it makes complex behavior manageable.
Example: Instead of separate refs isLoading.value = false; data.value = results, use a strongly typed state.value = { ...state.value, type: success, data: results }
Rules: Stick to domain vocabulary and canonical names. Never abbreviate (index instead of idx) and never include the type in the name (names instead of nameArray). Choose names that make sense in the domain of the module, not the technology or format you use (audio instead of file or result). Where possible, use the same (canonical) name for the same data, beyond module boundaries. When working with API types, always use the original API name instead of renaming it.
Consistent names convey the intention and scope of your code and make it easier to trace data-flows.