Architecting Large Frontend Applications

Introduction

Architecture, at its core, is the practice of making the invisible visible — drawing the boundaries, naming the dependencies, making deliberate decisions about what belongs where and why, so that the next person who opens the codebase can understand not just what it does but why it is shaped the way it is. And in frontend development, the need for this kind of thinking has grown faster than the industry’s ability to teach it.

What Architecture Is — and What It Is Not

Architecture is not the choice of framework. Choosing Angular over React is a technology decision. It is an important one with long-term consequences, but it is not, by itself, architecture.

Architecture is not the folder structure. How you organize your files is a structural decision that reflects your architecture, but it is a symptom, not the thing itself.

Architecture is not a diagram on a whiteboard. The diagram is a representation of the architecture.

Architecture is the set of decisions that are hard to change later — decisions about how the application is divided into parts, how those parts communicate, what each part is responsible for, and what dependencies are allowed to exist between them. These decisions shape every feature that is built after them. They are the frame that all the other work hangs on.

In one simple sentence, architecture is the answer to the question “how does the whole thing fit together and why?” If you cannot answer that question about a codebase, it either has no architecture or the architecture exists but is not visible — which is functionally the same problem.

The Prerequisites

Architecture thinking is not something you can jump to from the beginning of your career, and I think it is worth being honest about that rather than pretending it is accessible to any developer at any level. It requires a foundation, and the foundation has specific components.

Deep experience with at least one domain

You cannot make good architectural decisions about a domain you do not understand deeply. You will make decisions that seem structurally sound but that turn out to be wrong because they do not accommodate how the domain actually behaves under real conditions.

The developer who has built complex Angular applications for three years understands things about component communication, change detection at scale, module boundaries, and the realistic costs of different state management approaches that someone who has used Angular for six months simply does not. That knowledge is the raw material that architectural decisions are made from.

Before you start thinking architecturally about a domain, spend enough time in it that you have encountered its real edge cases — not the happy path, but the cases where the obvious approach broke down and you had to understand why.

Understanding of what makes code painful to change

Architecture is fundamentally about managing change. Every large application will need to change — requirements change, frameworks evolve, teams grow, performance problems are discovered. The quality of an architecture is measured by how well it accommodates change without requiring rewrites.

You cannot develop intuition for this without having experienced it. The developer who has had to change a data model that was scattered through twenty files without abstraction, who has had to add a new API call to a component that directly mixed HTTP logic with rendering logic, who has had to test a module that had hidden dependencies on global state — that developer has the empirical foundation for understanding what good architecture prevents.

Pain is the teacher here. You need to have felt the friction before you can design systems that avoid it.

Comfort with the gap between local and global thinking

The hardest mental shift in moving toward architectural thinking is the shift from local to global — from “how do I solve this problem” to “how does solving this problem affect the shape of the whole system.”

A developer writes a component. The component works. Good. An architect looks at the same component and asks: what does it depend on? What depends on it? If I change its interface, what breaks? Is the concern it is encapsulating at the right level of abstraction? Could ten teams build ten components like this without creating a coordination problem?

These questions are not natural to ask when you are trying to ship a feature by Friday. They become natural gradually, as you develop the habit of stepping back from the immediate problem and looking at where it sits in the larger system.

That habit is built by deliberately practicing the global view — by regularly reading code you did not write and asking how it fits into the whole, by reviewing architecture decisions from the perspective of the whole system rather than the immediate feature, by spending time with diagrams and dependency graphs rather than only with individual files.

The Mental Models That Change How You See Code

Architectural thinking is built on a set of mental models. These are the ones I find myself using most consistently.

Dependency direction is architecture

In any codebase, every module depends on other modules. Those dependencies form a directed graph. The shape of that graph is the architecture.

The critical insight: dependencies should point in one direction — from the volatile to the stable. High-level business logic should not depend on low-level implementation details. The component should not know which HTTP library fetches its data. The service should not know which UI framework renders its results. The business rule should not know about the database schema.

When dependencies point the wrong direction — when your core business logic imports from a UI library, or when your data layer imports from your routing layer — you have created a coupling that makes both harder to change.

// ❌ Wrong direction — business logic depends on infrastructure detail
class TicketService {
  constructor(
    private http: HttpClient, // Angular's HTTP client — infrastructure concern
    private router: Router, // Angular's router — framework concern
    private store: Store // NgRx store — state management concern
  ) {}

  async resolveTicket(id: string) {
    await this.http.put(`/api/tickets/${id}/resolve`).toPromise();
    this.store.dispatch(ticketResolved({ id }));
    this.router.navigate(['/tickets']); // business logic routing — wrong layer
  }
}
// ✅ Right direction — business logic depends on abstractions
// Concrete implementations injected from outside

interface TicketRepository {
  resolve(id: string): Promise<void>;
}

interface NavigationPort {
  goToTicketList(): void;
}

class TicketService {
  constructor(
    private repository: TicketRepository, // abstraction — not the HTTP client
    private navigation: NavigationPort // abstraction — not the router
  ) {}

  async resolveTicket(id: string) {
    await this.repository.resolve(id);
    this.navigation.goToTicketList();
  }
}

// The implementations are injected by the composition root
// Business logic never imports from Angular's HTTP module directly

This is the Dependency Inversion Principle applied to frontend. The TicketService does not know it is running in Angular. It does not know whether resolve makes an HTTP call or updates a local store. It knows what it needs and what it does, and the infrastructure that satisfies those needs is provided from outside.

This sounds like over-engineering for a small application. At scale — with multiple teams, frequent refactoring, and the real possibility that your HTTP strategy or state management approach will change — it is what makes the codebase malleable rather than rigid.

Boundaries define ownership

Every large application has natural seams — places where the system can be divided into pieces that communicate through well-defined interfaces. Finding those seams is one of the core architectural activities.

The question is: where are the natural boundaries?

Boundaries in frontend applications usually align with:

  • Domain boundaries — accounts, billing, notifications, settings are different domains. Each should have an internal structure and an external interface, and the implementation details inside should not leak across the boundary.
  • Team ownership boundaries — if two teams own different parts of the application, there should be a visible boundary at that point that makes the ownership explicit and the interface between them deliberate.
  • Volatility boundaries — things that change together should be grouped together. Things that change at different rates should be separated. The UI layer changes more frequently than the business logic layer, which changes more frequently than the data model. Organise accordingly.
  • Framework boundaries — the parts of your code that depend on Angular or React should be as thin as possible, and should sit at the boundary between your framework-agnostic business logic and the framework-specific infrastructure.

The stability-volatility spectrum

Not all parts of a codebase change at the same rate. Understanding where different things sit on the stability-volatility spectrum is a core architectural skill.

At the stable end: core domain models, fundamental business rules, data transformation utilities, validation logic. These change rarely, and when they do, the changes are significant and deliberate.

At the volatile end: UI components, feature flags, API response shapes, routing configurations. These change frequently as the product evolves.

The architectural implication: stable things should not depend on volatile things. Your business logic should not import from your UI layer. Your domain models should not import from your API client. When volatile things change — and they will — the dependencies should be structured so that the blast radius is contained to the volatile layer and does not propagate into the stable core.

The Patterns That Matter at Scale

These are the structural patterns I have found most valuable across large frontend codebases. They are not framework-specific — they apply equally to Angular and React, and the concepts apply beyond JavaScript.

Feature-first folder structure

Small projects can use a type-based folder structure without pain:

src/
  components/
  services/
  models/
  utils/

At scale, this produces folders with dozens of files that have no relationship to each other except their technical type. The services/ folder contains the authentication service, the ticket service, the analytics service, the formatting service — completely unrelated things in the same directory.

Feature-first structure organises code by domain instead:

src/
  core/
    auth/
    http/
    error-handling/
  features/
    tickets/
      components/
        ticket-list/
        ticket-detail/
        ticket-form/
      services/
        ticket.service.ts
        ticket-query.service.ts
      models/
        ticket.model.ts
        ticket-status.enum.ts
      store/
        ticket.actions.ts
        ticket.reducer.ts
        ticket.selectors.ts
      tickets.module.ts          ← public API for the tickets feature
      index.ts                   ← what the rest of the app can import
    accounts/
      ...same structure
    monitoring/
      ...same structure
  shared/
    ui/
      button/
      data-table/
      modal/
    utils/
    pipes/
  shell/
    navigation/
    layout/

The discipline is the index.ts (or the module file in Angular) at the feature root. This is the boundary. Other features import from the index, not from deep inside the feature. The internals of a feature are private. If tickets needs to display account information, it imports from features/accounts/index.ts, not from features/accounts/services/account-query.service.ts directly.

This single discipline — the barrel export as a boundary — enforces encapsulation at the feature level without requiring a separate build or a monorepo.

// features/tickets/index.ts — the public API
// Only export what other features and the shell need to use
export { TicketsModule } from './tickets.module';
export { Ticket, TicketStatus } from './models/ticket.model';
export { TicketFacade } from './services/ticket.facade';
// Internal services, components, reducers are NOT exported
// They are private to the feature

The facade pattern

The facade is the single most valuable pattern I have introduced in large Angular applications and the one that most consistently reduces complexity in ways that the whole team benefits from.

A facade is a service that sits at the boundary of a feature and exposes exactly what the rest of the application needs from that feature — and nothing more. It hides the state management, the HTTP calls, the caching, the error handling.

// The facade — the only thing the outside world talks to
@Injectable({ providedIn: 'root' })
export class TicketFacade {
  // What the shell and other features need from the tickets domain
  tickets$ = this.store.select(ticketSelectors.all);
  selectedTicket$ = this.store.select(ticketSelectors.selected);
  isLoading$ = this.store.select(ticketSelectors.isLoading);
  error$ = this.store.select(ticketSelectors.error);

  constructor(
    private store: Store,
    private ticketService: TicketService
  ) {}

  loadTickets(accountId: string): void {
    this.store.dispatch(ticketActions.loadRequested({ accountId }));
  }

  selectTicket(id: string): void {
    this.store.dispatch(ticketActions.selected({ id }));
  }

  resolveTicket(id: string): void {
    this.store.dispatch(ticketActions.resolveRequested({ id }));
  }
}
// A component in a different feature that needs ticket data
@Component({
  /* ... */
})
export class DashboardComponent {
  // The component knows nothing about NgRx, HTTP, or caching
  // It knows about the TicketFacade
  openTickets$ = this.ticketFacade.tickets$.pipe(
    map((tickets) => tickets.filter((t) => t.status === 'open'))
  );

  constructor(private ticketFacade: TicketFacade) {}

  resolve(id: string) {
    this.ticketFacade.resolveTicket(id);
  }
}

The facade is the seam between the feature’s internal implementation and the external world. When you refactor the state management from NgRx to a simpler service — or from a simple service to NgRx because the feature grew — you change the facade’s implementation without changing the component that uses it.

Presentational and container components — still the most important component pattern

This pattern is old enough that it gets dismissed as obvious, but I have seen it violated consistently enough in large codebases that it is worth stating clearly.

Container components (sometimes called smart components or connected components) know about state management. They dispatch actions, they select from the store, they call services. They do not contain significant UI logic.

Presentational components (sometimes called dumb or pure components) know nothing about state management. They receive data through inputs and emit events through outputs. They are pure functions of their inputs — the same inputs always produce the same rendered output.

// ❌ A component that does too much — mixed concerns
@Component({
  template: `
    <div *ngFor="let ticket of tickets$ | async">
      <h3>{{ ticket.subject }}</h3>
      <button (click)="resolve(ticket.id)">Resolve</button>
    </div>
  `,
})
export class TicketListComponent implements OnInit {
  tickets$ = this.store.select(selectAllTickets); // state concern

  constructor(
    private store: Store,
    private analyticsService: AnalyticsService // side-effect concern
  ) {}

  ngOnInit() {
    this.store.dispatch(loadTickets());
    this.analyticsService.track('ticket_list_viewed'); // mixed in
  }

  resolve(id: string) {
    this.store.dispatch(resolveTicket({ id }));
  }
}
// ✅ Container — orchestrates, does not render
@Component({
  template: `
    <app-ticket-list
      [tickets]="tickets$ | async"
      [isLoading]="isLoading$ | async"
      (resolve)="onResolve($event)"
    >
    </app-ticket-list>
  `,
})
export class TicketListContainer implements OnInit {
  tickets$ = this.ticketFacade.tickets$;
  isLoading$ = this.ticketFacade.isLoading$;

  constructor(
    private ticketFacade: TicketFacade,
    private analyticsService: AnalyticsService
  ) {}

  ngOnInit() {
    this.ticketFacade.loadTickets(this.accountId);
    this.analyticsService.track('ticket_list_viewed');
  }

  onResolve(id: string) {
    this.ticketFacade.resolveTicket(id);
  }
}

// ✅ Presentational — pure, testable, reusable
@Component({
  selector: 'app-ticket-list',
  template: `
    <app-loading-spinner *ngIf="isLoading"></app-loading-spinner>
    <div *ngFor="let ticket of tickets">
      <h3>{{ ticket.subject }}</h3>
      <button (click)="resolve.emit(ticket.id)">Resolve</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush, // safe because inputs are immutable
})
export class TicketListComponent {
  @Input() tickets: Ticket[] = [];
  @Input() isLoading = false;
  @Output() resolve = new EventEmitter<string>();
}

The presentational component is trivially testable — pass it inputs, assert on the rendered output. It is reusable anywhere in the application because it has no dependencies on state management. It is performant because OnPush change detection is safe — its inputs never mutate. And it is maintainable because the rendering concern is completely separated from the data concern.

State Management Architecture — The Hardest Decision at Scale

State management is where most large frontend applications develop their most intractable problems, because the decisions made early about how state flows through the application are extraordinarily hard to change later.

The three sources of state — and keeping them separate

Every piece of state in a frontend application belongs to one of three categories, and mixing them is a primary source of complexity.

Server state — data that lives on the server and is displayed in the UI. Tickets, users, account profiles. This state needs to be fetched, cached, invalidated, and kept in sync with the server. Libraries like TanStack Query (React) or Angular’s signal-based patterns are purpose-built for this.

UI state — state that is entirely local to the frontend and the server does not care about. Modal open/closed, selected tab, expanded accordion, current scroll position. This state is transient and should live as close to the UI that uses it as possible — usually in the component itself.

Shared application state — state that genuinely needs to be shared across multiple features. Current user session, global notification queue, active feature flags. This is what NgRx and Redux are actually designed for, and it is a much smaller category than most applications treat it as.

// ❌ The common mistake — everything in the global store
// Tickets loading state, selected tab, modal open state,
// current user, notification queue — all in NgRx

// ✅ The architectural approach — state in the right layer

// Server state — fetch and cache with the right abstraction
@Injectable()
class TicketQueryService {
  // Angular signals approach for server state
  private _tickets = signal<Ticket[]>([]);
  private _loading = signal(false);
  tickets = this._tickets.asReadonly();
  loading = this._loading.asReadonly();

  async loadForAccount(accountId: string) {
    this._loading.set(true);
    try {
      const data = await firstValueFrom(
        this.http.get<Ticket[]>(`/api/accounts/${accountId}/tickets`)
      );
      this._tickets.set(data);
    } finally {
      this._loading.set(false);
    }
  }
}

// UI state — in the component that owns it
@Component({
  /* ... */
})
class TicketDetailComponent {
  // Local component state — does not need to be in NgRx
  isExpanded = signal(false);
  activeTab = signal<'details' | 'comments' | 'history'>('details');

  toggle() {
    this.isExpanded.update((v) => !v);
  }
}

// Shared application state — genuinely global, in NgRx
// Current user session — multiple features need this
// Global notifications — shell renders them, any feature can dispatch
// Feature flags — affects rendering decisions everywhere

Unidirectional data flow as architecture

Whether you are using NgRx, Redux, or a custom signals-based approach, the architectural principle is the same: data flows in one direction.

User Action

Action / Event

Reducer / State Update (pure function)

New State

View (derived from state)

User sees updated UI → User Action → ...

The view is always a function of the state. The state changes through defined, traceable actions. There are no side channels — no direct mutations, no events that bypass the state, no component that reaches into another component’s state. Every state change has a cause that can be traced.

This principle makes large applications debuggable. You can look at the current state, find the last action that produced it, and understand exactly what happened. Without it, debugging a state inconsistency in a large application is archaeology — digging through layers of mutations to find the one that went wrong.

The Transition from Developer to Architect

This is the part I want to be most honest about, because it is often described as a role transition when it is actually a perspective transition — and one that cannot be rushed.

It happens through accumulated pain, not through study

You cannot read your way to architectural thinking. You can read your way to knowing the vocabulary, the pattern names, the framework options. But the judgment — the ability to look at a system and see both its current structure and the future problems it will create — comes from having built systems that created those problems and then having to fix them.

The developer who has maintained a codebase where there were no boundaries and everything depended on everything else has felt what a lack of architecture produces. That feeling is the foundation of architectural judgment. Without it, the architectural decisions you make are theoretical — you are applying patterns you read about to problems you have not personally experienced. The patterns may be correct, but the confidence with which you hold them will be fragile, and the first time a colleague pushes back, you will not have the empirical grounding to hold your position with conviction.

This is not an argument for accepting bad codebases. It is an argument for paying close attention to why they are bad — for learning from the pain rather than just escaping it.

The questions that shift your perspective

The practical transition from developer thinking to architect thinking is a shift in the questions you habitually ask when you look at code.

Developer questions:

  • Does this work?
  • How do I implement this feature?
  • What is the correct syntax for this?
  • How do I fix this bug?

Architect questions:

  • What will make this hard to change in six months?
  • What happens when this team grows from two people to six?
  • If this dependency changes, what else breaks?
  • What assumptions does this code make about its context?
  • What is the simplest thing that could work here and still be maintainable?
  • If a new developer joins tomorrow, can they understand this without me explaining it?

The architect questions are not harder than the developer questions. They are different — they require lifting your gaze from the immediate problem to the shape of the system around it. Developing the habit of asking them takes deliberate practice.

The way I developed that habit was simple and I still use it: after writing or reviewing any significant piece of code, I spent five minutes asking “what would have to be true for this to cause a problem?” Not immediately — in six months, in a year, when the team is larger, when the requirements change, when this dependency is updated. The ability to see the future failure modes of present decisions is the core skill of architectural thinking, and it is built by practising the question.

The skills that do not teach themselves

Reading diagrams and systems. An architect needs to hold a mental model of the whole system, not just the part they are currently working on. Practice drawing dependency diagrams of codebases you work in — not to produce documentation, but to force yourself to see the relationships between things. When you can draw the dependency graph from memory, you understand the architecture.

Making decisions under uncertainty. Architectural decisions have to be made before all the information is available. You will not know exactly how the requirements will evolve. You will not know exactly how large the team will become. You will not know exactly which performance constraints will matter. The skill is making decisions that leave room for the information you do not yet have — preferring reversible decisions over irreversible ones, building seams in the places where change is most likely, being explicit about the assumptions your decisions rest on.

Writing architecture decision records. An Architecture Decision Record (ADR) is a short document that captures a significant architectural decision, the context that led to it, the alternatives considered, and the trade-offs accepted. Writing them is a forcing function for thinking clearly about decisions. Reading them — your own past ones, especially — is how you understand the evolution of a codebase and the reasoning that produced its current shape.

# ADR-012: Feature-First Folder Structure

## Status

Accepted — 2024-08-15

## Context

The codebase has grown to 40,000 lines across 6 feature domains.
The current type-based structure (components/, services/, models/)
means that files related to the same feature are scattered across
multiple directories. New developers report difficulty understanding
which files belong to which feature.

## Decision

Reorganize to a feature-first structure where all files related to
a feature domain live under features/{domain}/ with a public API
exposed through an index.ts barrel export.

## Alternatives considered

- Keep the current structure and improve documentation: rejected —
  documentation decays; structure enforces.
- Monorepo with separate packages per feature: considered for future,
  premature now given the team size and deployment model.

## Consequences

- New developers can find all code related to a feature in one place
- Imports between features go through the public API, making coupling visible

* Initial migration effort: estimated 3 developer-days
* The barrel export convention needs to be documented and enforced in CI

Writing this kind of record is not bureaucracy. It is how the team’s architectural knowledge survives personnel changes. The developer who made the decision will not always be available to explain it. The ADR is what remains.

Seeing the Bigger Picture — Practically

The biggest picture question in frontend architecture is usually not asked often enough: what does this application need to be in three years, and are the decisions we are making today compatible with that?

Not because you can predict what the requirements will be in three years. You cannot. But the question forces you to think about what the application needs to be capable of — capable of being extended, capable of being maintained by a larger team, capable of accommodating different technology choices, capable of scaling to more users, capable of being tested reliably.

The fitness functions

A useful concept from evolutionary architecture: fitness functions are automated checks that verify the architecture is being maintained as the codebase grows.

In practical frontend terms, these look like:

// In a test file — architectural rules enforced automatically

describe('Architecture constraints', () => {
  it('features should not import from each other directly', async () => {
    // Use a tool like dependency-cruiser to check import paths
    const violations = await checkImports({
      forbidden: [
        // Tickets should not import from accounts internals
        { from: 'features/tickets/**', to: 'features/accounts/components/**' },
        { from: 'features/tickets/**', to: 'features/accounts/services/**' },
        // Only allowed through the public API
      ],
    });
    expect(violations).toHaveLength(0);
  });

  it('core should not depend on features', async () => {
    const violations = await checkImports({
      forbidden: [{ from: 'core/**', to: 'features/**' }],
    });
    expect(violations).toHaveLength(0);
  });

  it('presentational components should not inject services', async () => {
    // Custom lint rule that checks @Component classes
    // with no @Input() decorators for constructor injection
    const violations = await checkPresentationalComponents();
    expect(violations).toHaveLength(0);
  });
});

These tests fail when someone accidentally violates an architectural constraint. They make the architecture enforced rather than aspirational — the decision does not depend on developers remembering to follow it, it depends on the build failing if they do not.

Tools like dependency-cruiser and eslint-plugin-import can encode these constraints as configuration. The effort to set them up is small. The effort they save — catching accidental coupling before it becomes load-bearing — is large.

The performance budget as architecture

Performance is an architectural concern, not a feature. The decisions that affect performance — bundle splitting strategy, lazy loading boundaries, rendering strategy, caching architecture — are made at the architectural level and are expensive to change after the fact.

A performance budget is a set of constraints that the architecture must satisfy:

// A webpack performance budget — build fails if exceeded
module.exports = {
  performance: {
    hints: 'error',
    maxAssetSize: 250_000, // 250kb per asset
    maxEntrypointSize: 500_000, // 500kb for initial load
  },
};

The budget forces architectural decisions: code-splitting at the right boundaries, lazy loading of non-critical features, careful management of shared dependencies. Without a budget, the application grows until performance becomes a problem, at which point the architecture decisions that caused it are deeply embedded and expensive to change.

The Architect’s Toolkit

The transition to architectural thinking requires a different set of tools than the developer’s toolkit. Some are technical, some are not.

Dependency graph visualization. Tools like dependency-cruiser or Nx’s built-in dependency graph give you a visual representation of what imports what. Looking at the graph of a large application regularly is how you notice when the coupling is growing before it becomes a crisis.

The strangler fig pattern. When a large application needs to be refactored or migrated, the strangler fig pattern is often the right approach: build the new structure alongside the old, gradually migrate pieces to the new structure, and remove the old structure only when it has been fully replaced — not as a big bang rewrite, but as a gradual strangling of the old structure by the new one.

The C4 model. A four-level model for visualising software architecture: system context, containers, components, and code. Using it forces you to think about the system at different levels of abstraction, which is exactly the skill architectural thinking requires. Draw the C4 diagrams for your application. They will show you things about the structure that reading the code does not.

Technical debt mapping. Not all debt is equal. Some technical debt is in a part of the codebase that never changes and never causes problems — leave it. Some technical debt is in a critical path that every feature touches — address it immediately. A technical debt map is a periodic exercise of cataloging the known problems and prioritizing them by the friction they create, not just by how wrong they feel aesthetically.

Conclusion

Architecture is not a role that is assigned to you when you have accumulated enough seniority. It is a way of seeing code that develops gradually as you ask different questions, accumulate different kinds of pain, and develop the habit of looking at the system rather than only the component.

The prerequisites are real: you need domain depth before architectural decisions make sense. You need to have felt the friction of codebases without boundaries before you can design boundaries that prevent it. You need comfort with global thinking before local decisions feel connected to a larger shape.

But the transition is not a cliff. It is a gradient. Every time you ask “will this be hard to change in six months,” you are practicing the architectural perspective. Every ADR you write — even informally — is developing the habit of making decisions explicit. Every dependency graph you draw is building the mental model of the whole system that architecture requires.

The developers I have watched make this transition most successfully were not the ones who read the most architecture books. They were the ones who developed the clearest sense of the cost of the decisions they made — who felt, viscerally, what bad architecture produces, and who learned to project that feeling forward into the decisions they were making today.

Develop the questions. The rest follows.