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

  1. What this module is and what it is not
  2. The TaskFlow component tree, in full
  3. One-line responsibility, per component
  4. Data flow: properties downward
  5. Data flow: events upward
  6. The deliberate exception: the filter context
  7. Final project folder structure
  8. From folder to lesson: where each piece was explained
  9. Towards building the components

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.js

src/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.

  1. 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

  1. 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/context is 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

  1. 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.
  2. Explain, based on section 5, why <task-list> needs to add idTarea to the detail of the tarea-cambiada event it relays, when <task-card>, the event's origin, never included that data in its own.
  3. A teammate proposes moving contador-tiempo-restante-controller.js to src/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), why src/controllers/ remains the most suitable location even though, for now, only one component uses it.

Solutions

  1. 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> (event tarea-cambiada in two hops), and <task-board><task-filter> / <task-board><task-list> (filter context, both directions for <task-filter>, read-only for <task-list>).
  2. <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 only nuevoEstado in its detail) reflects exactly that deliberate ignorance. <task-list>, on the other hand, does know the structure of the tareas array (thanks to the closure over tarea.id captured while iterating the array with repeat), 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.
  3. ContadorTiempoRestanteController is 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 a fechaLimite property, not only by <task-card>. Placing it in src/components/ would suggest that it is, itself, a custom element registered with customElements.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

Module 2: Reactive Templates and Rendering

Module 3: Reactive Properties and State

Module 4: Styling Lit Components

Module 5: Events and Component Communication

Module 6: Lifecycle and Advanced Behavior

Module 7: Directives and Advanced Template Features

Module 8: Integration, Interoperability and Deployment

Module 9: Testing and Best Practices

Module 10: Project: Building TaskFlow

© Copyright 2026. All rights reserved