The last lesson of module 9 announced it with these exact words: "there is a single lesson left, the last one of the course: 'Project: Building TaskFlow', where no new concept is presented, but the application is walked through from start to finish". That "single lesson" is, in reality, this entire module, split into four parts for ease of reading, but conceived as a single narrative unit: the final project. This first part does not write any component yet; it draws the complete map of TaskFlow as it stands after nine modules —its component tree, how data travels between them, and the project's final folder structure— so that the next three lessons have a clear blueprint to build on, instead of reconstructing that overall picture on the fly.
Contents
- What this module is and what it is not
- The TaskFlow component tree, in full
- One-line responsibility, per component
- Data flow: properties downward
- Data flow: events upward
- The deliberate exception: the filter context
- Final project folder structure
- From folder to lesson: where each piece was explained
- Towards building the components
- What this module is and what it is not
It is worth settling this before writing a single line of code: this module introduces no Lit concept that has not already been explained between modules 1 and 9. Templates, reactive properties, styles with Shadow DOM, custom events, lifecycle, controllers, mixins, directives, shared context, integration with the rest of the ecosystem, and best practices: all of that is already covered. What is indeed missing, and is exactly the goal of this module's four lessons, is seeing it all joined together, as a single coherent project, rather than as a succession of code fragments spread across thirty-six different lessons, each focused on the technique it was its job to explain at the time.
This first lesson is limited to planning: the final component tree, the data flow that connects them, and the folder structure. Lessons 10-02 and 10-03 write the complete code for each component and each piece of shared infrastructure, and 10-04 closes with tests, bundling, and the course's own closing.
- The TaskFlow component tree, in full
Throughout the course, TaskFlow has grown into five custom components, organized in a tree four levels deep:
<task-board> (root of the application)
├── <task-filter> (sibling of <task-list>)
└── <task-list>
└── <task-card> (one per task, repeated with repeat + key)
└── <user-avatar>No component in this tree is new: <task-board> appeared in module 5 (05-04) as the common ancestor of <task-list>; <user-avatar> in module 4 (04-04), as a child of <task-card>; and <task-filter>, mentioned since module 5 itself without being implemented, did not truly come to life until module 7 (07-04), by way of @lit/context. The only thing this lesson contributes is finally drawing the complete tree all at once, something no earlier lesson needed to do because each one focused on the piece it was building at that moment.
- One-line responsibility, per component
Each TaskFlow component has a responsibility that can be summed up in a single sentence, without needing to list every detail of its implementation:
| Component | One-line responsibility |
|---|---|
<task-board> |
Root orchestrator: keeps the tareas array, publishes the filter context, and manages the initial load and the state changes that bubble up from its descendants. |
<task-filter> |
Provides the text-search and status-search controls, and writes changes directly into the shared filter context. |
<task-list> |
Consumes the filter context, decides which tasks match the active criterion, and renders them efficiently with repeat and a stable key. |
<task-card> |
Displays the data for a single task, allows its status to be changed, and gives a visual warning when that task is close to its deadline. |
<user-avatar> |
Displays the person assigned to a task, with a real image or with their initials as a fallback, by means of distributed content. |
This table, however trivial it may look, is proof that TaskFlow's breakdown into five pieces, carried out over the course, follows the criterion explained in the lesson "Common Patterns and Anti-patterns" (09-04, section 6): each component changes for a reason different from the others', and none of them needs the conjunction "and" to describe itself.
- Data flow: properties downward
TaskFlow's first communication axis, established since module 3 and refined in module 5, is the flow of reactive properties from a component to its direct children in the template:
| From | To | What goes down |
|---|---|---|
<task-board> |
<task-list> |
.tareas (the full array, not already filtered; unfiltered: the filtering happens inside <task-list>, based on the context) |
<task-list> |
<task-card> |
.titulo, .estado, .prioridad, .urgente, .fechaLimite, asignado-a, asignado-imagen, one instance per task in the array, with tarea.id as the repeat key |
<task-card> |
<user-avatar> |
The nombre attribute, and optionally an image distributed via <slot> instead of a property |
No row in this table is new: the first one was established in the lesson "Sibling Component Communication Patterns" (05-04), the second in "Parent-to-Child Communication with Properties" (05-03) and was refined with repeat in "Shared Context with @lit/context" (07-04), and the third in "Slots and Styling Distributed Content" (04-04). What this lesson contributes is, once again, seeing them together as a single continuous downward flow, from the root of the tree to its deepest leaf.
- Data flow: events upward
The second axis, symmetric to the previous one, is that of custom events that bubble up from where an interaction happens to whoever has the authority to decide what to do with it:
<task-card> --tarea-cambiada (detail: { nuevoEstado })-->
<task-list> --tarea-cambiada (detail: { idTarea, nuevoEstado })-->
<task-board>This two-hop relay, with idTarea explicitly added in the second event because <task-board> has no access to the closure over tarea.id that <task-list> does have, was established in full in the lessons "Custom Events: Child-to-Parent Communication" (05-02) and "Sibling Component Communication Patterns" (05-04). <task-board> is, at all times, the only TaskFlow component that actually modifies the tareas array; all the others merely announce facts that happened to themselves, never directly modifying an ancestor's state.
- The deliberate exception: the filter context
The two previous axes —properties downward, events upward— cover all of TaskFlow's communication except for one relationship: the one connecting <task-filter> with <task-list>, two sibling components with no direct parent-child relationship. As explained in the lesson "Shared Context with @lit/context" (07-04), resolving that relationship through the pattern from the two previous sections would force <task-board> to manually relay, on every keystroke in the filter, a property toward <task-list> that <task-board> itself has no need for whatsoever.
<task-board> (ContextProvider of filtroContext)
↑↓ ↑↓
<task-filter> <task-list>
(ContextConsumer, writes) (ContextConsumer, reads)<task-board> publishes the context a single time, in its constructor; <task-filter> consumes it both to read and to write (through the actualizar function included in the context's own value); <task-list> consumes it only to read, and uses that value to decide which tasks match the filter before passing them, now as a normal property, down to each <task-card>. Neither sibling ever knows about the other: both speak only to the context that <task-board> publishes, exactly the mechanism studied in module 7.
- Final project folder structure
With the component tree and the two communication axes already laid out, TaskFlow's folder structure, as it stands at the end of the course, organizes each piece according to the kind of responsibility it fulfils, not according to the module in which it was introduced:
taskflow/
├── index.html
├── package.json
├── vite.config.js
├── web-test-runner.config.js
├── src/
│ ├── components/
│ │ ├── task-board.js
│ │ ├── task-filter.js
│ │ ├── task-list.js
│ │ ├── task-card.js
│ │ └── user-avatar.js
│ ├── controllers/
│ │ └── contador-tiempo-restante-controller.js
│ ├── mixins/
│ │ └── con-estado-carga.js
│ ├── context/
│ │ └── filtro-context.js
│ ├── directives/
│ │ └── resaltar-si-urgente.js
│ ├── services/
│ │ └── tareas-service.js
│ └── styles/
│ └── shared-styles.js
└── test/
├── task-card.test.js
└── task-filter.test.jssrc/components/ gathers the five custom elements from the tree in section 2; src/controllers/ and src/mixins/ separate, following the criterion from the lesson "Mixins and Behavior Composition" (06-04), logic with its own state and lifecycle (ContadorTiempoRestanteController) from logic that integrates directly into a component's public API (ConEstadoCarga); src/context/ holds the context key shared between <task-board>, <task-filter>, and <task-list>; src/directives/ keeps the custom directive resaltarSiUrgente; src/services/ isolates the simulated data loading from any specific component; and src/styles/ holds the styles shared across several components. test/ gathers the test suite built in module 9.
- From folder to lesson: where each piece was explained
This final table works as a reverse index: given a file from the structure above, it indicates in which lesson of the course the technique that file applies was first explained, useful as a quick reference during the next three lessons of this module.
| File or folder | Lesson where the technique was explained |
|---|---|
components/task-board.js (orchestrator, context, loading with until) |
05-04, 06-04, 07-03, 07-04 |
components/task-filter.js (context, accessibility) |
07-04, 09-02 |
components/task-list.js (repeat, consumer context) |
02-04, 07-04, 09-03 |
components/task-card.js (properties, events, controller, directive, accessibility) |
03-01 to 03-04, 05-02, 06-03, 07-02, 09-02 |
components/user-avatar.js (slots, CSS variables) |
04-03, 04-04 |
controllers/contador-tiempo-restante-controller.js |
06-03 |
mixins/con-estado-carga.js |
06-04 |
context/filtro-context.js |
07-04 |
directives/resaltar-si-urgente.js |
07-02 |
services/tareas-service.js |
07-03 |
styles/shared-styles.js |
04-02 |
test/*.test.js |
09-01, 09-02 |
- Towards building the components
With the tree, the two communication axes, the context exception, and the folder structure now settled, this lesson has fulfilled its role as a map. The next three lessons simply walk through it: first the components that display data, then the ones that orchestrate state, and finally the tests, the bundling, and the course's closing.
Common Mistakes and Tips
- Organizing folders by course module instead of by type of responsibility: a structure like
modulo-06/,modulo-07/would reflect the order in which each technique was learned, not how a real project is organized;components/,controllers/,mixins/,context/group code according to its role within the application, which is the criterion that survives once the course is over. - Forgetting that the filter context is an exception, not the general rule: as explained in section 6, the vast majority of TaskFlow's communication follows the pattern of properties downward and events upward;
@lit/contextis reserved for the one case —siblings with no direct relationship— that this pattern does not resolve well on its own. - Thinking that
<task-board>knows the internal structure of<task-card>:<task-board>only knows<task-list>(its direct child) and, through the context,<task-filter>; it has never had, nor does it need, any direct reference to a specific<task-card>, thanks to the event relaying explained in section 5. - Expecting this lesson to contain executable code: deliberately, it does not; it is a map for the next three lessons, not an implementation.
Exercises
- Without looking at the previous lessons, draw from memory the component tree from section 2 and, next to each arrow, write whether that relationship is resolved with a property, with an event, or with the filter context. Then check the result against sections 4, 5, and 6.
- Explain, based on section 5, why
<task-list>needs to addidTareato thedetailof thetarea-cambiadaevent it relays, when<task-card>, the event's origin, never included that data in its own. - A teammate proposes moving
contador-tiempo-restante-controller.jstosrc/components/, arguing that only<task-card>uses it. Explain, based on section 7 and on the criterion from the lesson "Mixins and Behavior Composition" (06-04), whysrc/controllers/remains the most suitable location even though, for now, only one component uses it.
Solutions
- The correct tree is the one from section 2:
<task-board>with two children,<task-filter>and<task-list>;<task-list>with one<task-card>per task; each<task-card>with a<user-avatar>. The relationships are:<task-board>→<task-list>(property.tareas),<task-list>→<task-card>(per-task properties),<task-card>→<user-avatar>(attribute and distributed content),<task-card>→<task-list>→<task-board>(eventtarea-cambiadain two hops), and<task-board>↔<task-filter>/<task-board>↔<task-list>(filter context, both directions for<task-filter>, read-only for<task-list>). <task-card>does not know the concept of a "task identifier": it is an isolated component that only knows about itself, and the event it dispatches (tarea-cambiada, with onlynuevoEstadoin itsdetail) reflects exactly that deliberate ignorance.<task-list>, on the other hand, does know the structure of thetareasarray (thanks to the closure overtarea.idcaptured while iterating the array withrepeat), and it is the only one that can add that information before the event keeps bubbling up; if it did not add it,<task-board>, which has no closure over which specific card fired the event, would have no way of knowing which array element it must update.ContadorTiempoRestanteControlleris not, in itself, part of any component's public API or template: it is an object with its own state and lifecycle, deliberately designed (as explained in 06-03) to be reusable by any future host that exposes afechaLimiteproperty, not only by<task-card>. Placing it insrc/components/would suggest that it is, itself, a custom element registered withcustomElements.define, which is not the case;src/controllers/correctly reflects its nature —a piece of reusable behavior, decoupled from any specific component— regardless of how many hosts use it today.
Conclusion
This lesson has drawn the complete map of TaskFlow without writing a single new component: the tree of five components with their one-line responsibility each, the two communication axes —properties downward, events upward— that resolve almost the entire application, the deliberate exception of the filter context for the one pair of siblings with no direct relationship, and the final folder structure that organizes each piece according to its role, not according to the module in which it was learned.
With this map now drawn, the next lesson, "Building the Main Components", starts filling it in for real: the complete, consolidated code for <user-avatar>, <task-card>, and <task-filter>, joining into a single file per component all the pieces that this very course kept adding piece by piece, module by module.
Lit Course
Module 1: Introduction to Lit and Web Components
- What are Web Components and why Lit?
- Setting Up the Development Environment
- Your First Lit Component
- Anatomy of a Lit Component
Module 2: Reactive Templates and Rendering
- Lit's Template Engine
- Expressions and Interpolation in Templates
- Conditional Rendering
- List Rendering
- The Rendering Cycle
Module 3: Reactive Properties and State
- Reactive Properties
- Internal State with @state
- Types of Properties and Custom Converters
- Attributes vs Properties and Reflection
Module 4: Styling Lit Components
- Encapsulated CSS with Shadow DOM
- Shared Styles Between Components
- Custom CSS Properties and Theming
- Slots and Styling Distributed Content
Module 5: Events and Component Communication
- Handling DOM Events in Templates
- Custom Events: Communication from Child to Parent
- Communication from Parent to Child with Properties
- Communication Patterns Between Sibling Components
Module 6: Lifecycle and Advanced Behavior
- Lifecycle Callbacks
- Reactive Hooks: willUpdate, updated, and firstUpdated
- Reactive Controllers
- Mixins and Composing Behavior
Module 7: Directives and Advanced Template Features
- Built-in Directives: classMap, styleMap and ifDefined
- Custom Directives
- Asynchronous Rendering with until
- Shared Context with @lit/context
Module 8: Integration, Interoperability and Deployment
- Using Lit Components in Plain HTML
- Integrating Lit with React, Vue, and Angular
- Server-Side Rendering with @lit-labs/ssr
- Bundling, Publishing, and TypeScript
Module 9: Testing and Best Practices
- Unit Tests with Web Test Runner
- Accessibility in Web Components
- Performance and Optimization
- Common Patterns and Anti-patterns
