Read the host before you touch it
A day of reading a codebase you did not write buys a map of seams — the places behaviour can change without editing in that place — and that map, not the source, is what decides where a graft can land.
You are handed a repository. It is a 2018 Angular application, roughly four hundred thousand lines, and the three people who designed it left two acquisitions ago. Your job is to put an agent review gate into it. You have a day before you owe someone an answer about how.
The instinct is to open the component directory and start reading components. That instinct is from ownership work, where reading the components is reading the system, because you wrote the conventions that make components the unit of meaning. Here it is close to useless. Four hundred thousand lines is more than you can read, the components will not tell you what you actually need to know, and at the end of the day you will have opinions about their naming conventions and no answer to the question you were asked.
The question you were asked is narrower than “how does this app work.” It is: where can something be inserted, and what will fight back. Those have a literature, and it is not front-end literature.
Seams: the only map worth drawing on day one
Michael Feathers named the concept in Working Effectively with Legacy Code, and Martin Fowler restates it precisely: a seam is “a place where you can alter behavior in your program without editing in that place”. Every seam has what Feathers calls an enabling point — “a place where you can make the decision to use one behavior or another.”
Read that definition twice, because the second half is what makes it useful to you specifically. A graft is exactly a behaviour change you are not allowed to make by editing in that place. You do not own the file. You may not be allowed to open a pull request against it. The host team may be shipping a release the week you land. Everything you can do has to happen at a seam, so an inventory of seams is not background research — it is the set of all possible answers.
Fowler lists three things seams are for: breaking dependencies to simplify testing, inserting probes to gain observability, and redirecting program flow to new modules as part of legacy displacement. That third one is your job description with the words changed.
The seams that matter in a web host are mostly not in the component tree:
- The HTML template that every page renders through. A layout file, a Razor
_Layout.cshtml, an Angularindex.html. One line added here reaches every route. - The routing table. A route registration is an enabling point by construction: adding an entry changes behaviour without editing any existing screen.
- The build configuration. An entry point, an externals map, a plugin. Powerful, and the most expensive seam to use — you will be asking the host team to change the thing they least want to change.
- The HTTP layer. A reverse proxy, a gateway, an existing interceptor. Ian Cartwright, Rob Horn and James Lewis call the technique that uses these Event Interception — “enabling events passing from one component to another to be intercepted and routed to new components/services.”
- Existing extension points the host already ships. A plugin registry, a slot system, a widget area, a documented
windowglobal. If one exists, it is almost always the right answer, and finding it is worth more than everything else in this list.
You are not modernising. You are a tenant.
The legacy-displacement literature you have just been handed is written by consultants whose engagement is to replace the host. Martin Fowler’s Strangler Fig Application — the pattern that gives the whole field its vocabulary — describes a system that “begins with small additions, often new features, that are built on top of, yet separate to the legacy code base,” and grows until the original is gone.
Take the technique. Reject the ending. The strangler fig kills the tree, and you are not killing this tree — you are one surface inside a product that the portfolio company will keep shipping and keep owning long after you leave. Fowler is explicit that the pattern buys its safety with transitional architecture that both systems tolerate while the replacement happens; a graft has no “while,” because the host is never going away. That changes what you are allowed to leave behind. A displacement engineer can install scaffolding and remove it later. You cannot: whatever you install is permanent, and somebody who has never met you will maintain it.
Where people get burned
The failure this produces is a graft that works and is hated. It needs a webpack config change, a bumped Angular version, a new entry in three route files and a shared global. Every one of those was reasonable in isolation, none of them are things the host team can undo, and the next person who touches the app finds four unexplained modifications with your name on them. The install footprint is a design constraint, not an implementation detail. Count it while you are still reading, before you are attached to an approach.
Read for constraints, not for comprehension
There is a comforting story where a day of careful reading gives you the original team’s intent back. It does not. Intent lives in the heads of the people who made the decisions, and in a host whose authors left two acquisitions ago there is nobody to ask and no comment that will substitute. What you would actually reconstruct in a day is a confident guess, and a confident wrong model of a host is worse than an honest thin one, because you will design against it.
So read for the things that are still true regardless of anyone’s intent. Facts, not understanding:
- Response headers. Load a page and read them.
Content-Security-Policyalone can eliminate half the delivery mechanisms available to you before you have written anything. This is the single highest-value ten minutes of the day. - The global stylesheet and reset. Not to read in full — to measure. How many rules target bare element selectors? Is there anything with
!important? Is there a@layeranywhere, or is it all one flat unlayered cascade? - The build and its lockfile. Which bundler, which major version, what is already shared, and what framework versions are actually installed — not what the README claims.
- The browser floor. Whatever the host targets is now your floor too, and it is frequently lower than you would choose.
- Who merges. Not a code fact, and the one most likely to decide the outcome. A seam you are not allowed to use is not a seam.
Retrieval check
You have one day in an unfamiliar 400k-line host. Why is reading the component directory close to the worst use of it?
Check your answer
Because components answer “how does this work,” and you were asked “where can something be inserted, and what will fight back.” Those are different questions with different sources. The insertion points are seams, and in a web host most seams live in the shared layout template, the routing table, the build config, the HTTP layer, and any extension point the host already ships — none of which are in the component directory.
There is a second reason. Reading four hundred thousand lines of someone else’s components will not reconstruct the intent behind them — the people who held it are gone — but it will reliably produce the feeling of having understood, which is worse than an honest map of constraints because you will design against it. Read instead for the facts that survive the authors’ departure: headers, the reset, the lockfile, the browser floor, the approver. Those are still true.
What a seam map actually looks like
Four columns, one row per candidate. Nothing more, because a longer format invites you to keep reading instead of deciding.
| Seam | Enabling point | Blast radius | Approver |
| ------------------------ | ----------------------- | -------------- | ------------ |
| Shared layout template | one <script> line | every route | platform |
| Route table | new route registration | one URL | app team |
| Existing widget registry | registry entry, no build | wherever used | app team |
| Webpack config | externals + entry | whole build | platform+CI |
| Reverse proxy | path rule | one URL prefix | infra |Read the last column before the first. The webpack row may be the technically cleanest option on the page and still be the wrong answer, because “platform team plus CI” is a two-week conversation in a company that has ninety of these to run. A worse mechanism at a cheaper seam ships. The best mechanism at an expensive seam becomes a slide about what you would have built.
Hands on
Map the seams of a host you did not write
Done when: ARTIFACT.md carries a seam map of at least five candidate insertion points in a real host codebase, each with its enabling point, blast radius and approver — and at least one row is marked unusable for a non-technical reason.
- Pick a real host. A former employer’s repo you still have, a large open-source application, a product you have consulted on. Not a fresh scaffold: a generated app has no constraints, and constraints are the entire subject. Record which one in
learning/grafting-ui/ARTIFACT.mdunder the three-hosts table. - Before opening any source file, load a page of it and write down its response headers, its global stylesheet size, its bundler and major version, and its stated browser floor. Time-box this to thirty minutes. These are the facts that survive the original team leaving.
- Find at least five seams — places behaviour can be altered without editing in that place. Push yourself past the component tree: look at the shared layout template, the routing table, the build config, the HTTP layer, and anything the host already ships as an extension point.
- Write the four-column table into
ARTIFACT.md. For blast radius, write what breaks if your code throws at that seam, not what it reaches when it works. - Now mark at least one row unusable, and give a non-technical reason: nobody will approve it, the team is mid- release, the config is generated, the file is owned by a group that does not know you exist. If no row earns that mark, you have not found the real constraints yet — go back to the approver column.
- Bring the map into the chat. I will push back hardest on any row where the blast radius reads like an aspiration rather than a failure mode.
What this does not cover
A seam map tells you where code can be inserted. It says nothing about what happens to that code once it is in there, and the first thing that happens is visual. Not a crash, not an error — your component renders, and it renders wrong, because the host’s global stylesheet reached inside it. The cascade lesson takes that apart: why style collision is the first failure of every graft, what cascade layers actually fix, what containment actually fixes, and the specific thing neither of them touches.
Choosing a mechanism from the constraints you have now collected is the subject of the delivery-constraints lesson, and it deliberately comes after the cascade — because style isolation is the axis most people rank the mechanisms on, and ranking it wrong makes the mechanism choice wrong too.
Read this next — primary source
Legacy SeamMartin Fowler, martinfowler.com, 4 January 2024 — free
Short enough to read in five minutes, and it is the concept this entire lesson is built on. What the full page adds is the catalogue of seam varieties Fowler links out to — HTTP proxies, dependency injection points, database triggers, feature toggles — each with its own enabling point. The catalogue matters because your instinct as a front-end engineer will be to look for seams only in the component tree, and most of the useful ones in a real host are in the build config, the routing table and the HTTP layer instead.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.