Repository navigation
Commit 4f29ed0
authored
refactor(dashboard)!: replace the server-rendered UI with React plugins (#99)
* feat(dashboard): let extensions report version and configured state
* fix(dashboard): attribute contributor status at registration instead of inferring it
* fix(dashboard): forget contributor status when unregistering through the recording registry
recordingRegistry overrode Register and RegisterRemote but not Unregister, so
a name freed through the registry an extension was handed kept pointing at the
old owner. Whoever claimed that name next served the previous extension's live
status.
forgetContributorStatus already existed and was already wired into
UnregisterRemoteContractContributor. This covers the other route that frees a
name, which is the one an extension reaches on its own.
* refactor(dashboard): delete the server-driven React shell and its 11MB embed
* refactor(dashboard): remove the graph kind from the contract transport
* refactor(dashboard): delete the graph model, slot merging and the component vocabulary
* test(streaming): drop the dead graph-route-count assertion
extensions/dashboard/contract just deleted ContractManifest.Graph
(the UI-graph model). This test lives outside the dashboard tree,
in the streaming extension's own module, and was the only other
consumer in the whole repo referencing that field. Its remaining
assertions (contributor name, intent count, warden validation)
still cover live behaviour and are untouched.
* refactor(dashboard): trim the pilot contributor to data intents only
The pilot manifest no longer describes screens. Cut the graph section
from manifest.yaml along with the navigation query and intent, since
the plugin host now builds the sidebar from each plugin's own nav
array instead of walking the contract registry.
navigation.go and its test go with it, they had no other purpose.
This also restores the build: navigation.go was the last reader of
ContractManifest.Graph after Task 6 dropped the field, and
types_test.go asserted a graph route count that no longer compiles.
Updated the intents count in that test to 12 and dropped the graph
assertion.
Seven data intents survive untouched: overview, health, metrics,
services, extensions.list, traces, audit.
* fix(dashboard): clear the graph residue tasks 6+7 review round 1 found
Delete four exported declarations with zero non-declaration references:
NavConfig, DataBinding (with its UnmarshalYAML), ExtensionTarget, and
PermissionsHash. The TestDataBinding_BothShapes and PermissionsHash
tests go with them, they had no other reason to exist.
Delete the streaming contract's 149-line graph: block, the largest
surviving graph document in the repo. It was already inert (yaml.Unmarshal
has no KnownFields, so the key was silently dropped) but left standing
it would read as live config to the next person through this file.
Drop ResponseMeta.RouteParams: no writer anywhere in the tree since
MatchRoute went, and no wire consumer in forge-dashboard either.
Add TestProjectAppHome_PrefixesWithSlug. The /@<slug><home> URL shape
projectAppHome implements lost its only assertion when
TestNavigationHandler_PrefixesHrefWithAppSlug died with navigation_test.go,
even though the behavior it drove survives in apps.list. Verified by
hand: a version of projectAppHome that skips prefixing fails the new
test, restored, passes clean.
Reworded ten comments and one doc string that still described deleted
graph/slot machinery (contract/doc.go, manifest.go, loader/validate.go,
loader/yaml.go, warden.go, transport/http.go, pilot/apps.go, aware.go)
and cleaned two loader test fixtures that still carried dead graph: YAML.
Also fixed the dashboard-contract-probe flag help text Task 5 left
stale, and a ragged comment reflow in registry.go's Unregister doc.
* fix(dashboard): stop redirecting the core paths into a prefix that no longer exists
Task 4 deleted the {base}/ui routes. Nothing checked who was pointing at them.
registerShellRedirects was still handing out ten 302s onto that prefix, and it
ran whenever LegacyUI was false, which is the default. So GET /dashboard sent
you to /dashboard/ui/ and /dashboard/ui/ was a 404. Same for health, metrics,
services, extensions, traces and the three metrics deep links.
Those ten registrations are gone. The core paths now 404 directly. That is the
honest answer until the prebuilt shell artifact lands: bouncing a browser
through a dead prefix tells you less than the 404 does. LegacyUI is untouched
and still off by default, so WithLegacyUI(true) remains the escape hatch and
still serves the templ pages at all ten paths. RootContributor still owns "/"
either way, since an embedded dashboard that claimed the root asked for that
explicitly and the redirects were never what made it work.
redirectTo and redirectTraceDetail went with the call sites. So did the
registerRootContributor fallback that 302'd a proxy-less remote into /ui/.
The three comments asserting the opposite are rewritten. config.go claimed the
shell stays mounted at {BasePath}/ui either way and that deep links keep
working, both false. extension.go and pages.go both described a React shell
serving six pages that is not there.
Also in this pass, all of it the same defect in prose:
- manifest.go said Query is a binding a contributor's React code references by
name. Nothing references it and nothing can. Plugins never see the Go
manifest; they call intents through the scoped client. Queries is read only
by loader.Validate's self-consistency check and QueryCache by nobody. The
section stays, the invented consumer does not.
- manifest.go and pilot/apps.go both told plugin authors their React routes
live under /@<slug>/*. definePlugin's own example declares /authsome/users
with no prefix, and apps.list has no TypeScript caller at all. The comments
now say the projection has no consumer and that a later wave picks one way
or the other. projectAppHome and its test stay; the function does what the
test says.
- contract/doc.go pointed at DESIGN.md as "the spec this implements". DESIGN.md
is the graph spec and says graph 18 times. Now marked as history.
- .gitignore kept three un-ignore rules for contract/shell/, deleted in 6925566.
loader.Validate's query-to-intent loop had no negative test while the doc
comment advertised it as one of four jobs. It has one now, four cases across
the looksCrossContributor boundary. Break the loop and two of them fail.
* docs(dashboard): stop the GoDoc promising a React shell that was deleted
Comments only. `git diff` with comment lines filtered out is empty.
The two that mattered most are exported GoDoc, which is what somebody reads to
decide how to configure their dashboard. WithRootContributor said empty "keeps
the shell redirect". WithLegacyUI said "The shell is still mounted at
{BasePath}/ui, so this does not remove it." Read that second one and you
conclude you can leave the flag off and still reach a UI at /ui. You cannot.
There is no shell, no /ui, and with the flag off the core paths 404. Both now
say that, and PagesConfig's near-verbatim copy of the struct comment says it
too. No wave is named for when /ui comes back, only "a later wave", because
this wave has now been bitten twice by a comment naming a wave that slipped.
Then I grepped for the rest of them, which is the lesson from the last round.
Nine more sites asserted a live shell. Six are the same claim wearing different
clothes: the principal endpoint, the CSRF endpoint and the RequiredRoles 403
were all documented as things "the React shell" renders. Those endpoints are
real and still serve. Their consumer is not, so they now say what they return
and note that nothing renders it yet.
aware.go was the worst of the nine and the reason this is not a cosmetic pass.
It tells auth-extension authors how to integrate, and it told them the shell
ships a LoginScreen and an AuthGate that picks between it and their own /login
page. Both deleted. An author following it wires a real DashboardAuthAware and
a real auth.login intent, then waits for a login screen that cannot appear.
Server side is unchanged and still correct; the client half now says plainly
that it does not exist and that registering is still worth doing.
pilot.go and apps.go described apps.list as feeding "the React shell's app
switcher". Same finding as Important 3 last round, which I fixed in the type
docs and missed in the registration site and the loop body. Both now match.
Left alone, deliberately. The `dashboard/ui/shell` package is the templ shell,
topbar and breadcrumbs and user dropdown, which is live and compiled and has
nothing to do with the React one. "Micro-frontend shell" on the Extension type
describes the extension itself. The `shellEnvelopes` wording on the
capabilities handler matches the JSON key the endpoint actually emits. And
tracing_middleware_test's {"/dashboard/ui", true} tests a prefix matcher, which
returns true for any subpath whether or not something serves it, so it asserts
nothing about /ui existing.
* feat(dashboard): serve the prebuilt shell behind a ShellSource switch
{base}/ui returns a page again. It has been a 404 since W3 deleted the
server-driven React shell, which is what we wanted at the time: that app was
committed into this repo and //go:embed all:dist pulled 11MB into every binary
importing the extension, 6MB of it sourcemaps.
The shell lives in forge-dashboard now and ships as a release tarball. Release
CI unpacks it over extensions/dashboard/shellassets/dist before it builds. What
is committed there is a placeholder: a small page that explains itself if you
hit it, and a README saying how to fetch the real thing by hand. //go:embed
needs files at build time, so without the placeholder a plain go build fails
offline and in every consumer's CI. IsPlaceholder() looks for a meta tag in that
page rather than checking a size, because a real build's size is not a stable
signal, and the extension logs a warning at startup when it finds one.
Two things in the handler look like details and are not. It injects
window.__FORGE_DASHBOARD__ before </head>, because the bundle cannot know at
build time where it is mounted. And it rewrites "./assets/ to one absolute URL
under the base path, on the bytes, before the response goes out. That second one
cannot move into the bootstrap. The browser resolves src and href while parsing
the document, so by the time any script of ours runs the wrong requests are
already gone and already 404ing.
ShellSource defaults to ShellEmbedded, and the serving path reads the zero value
"" as embedded too, because a Config built as a struct literal never passes
through DefaultConfig(). ShellExternal mounts nothing at all, for deployments
that build their own shell with their own plugins.
The bootstrap sets basePath, contractBase, shellBase, authEnabled and loginPath.
loginContributor and loginOp are left out on purpose. They were hardcoded to
authsome's values behind a comment about deployments overriding them later,
later never came, and nothing read them by the time the old shell went. W5
builds the login UI and can decide whether they belong in the bootstrap at all.
.gitignore needed a negation. A blanket dist/ rule was hiding the placeholder,
which would have left a fresh clone unbuildable with no obvious cause. The new
rules re-include index.html and README.md and nothing else, so an unpacked
artifact stays ignored even under git add -A.
* fix(dashboard): sweep the stale shell docs and tighten the static handler
pages.go still said nothing was mounted at {BasePath}/ui. Three places, and the
worst of them reasoned out loud about why no redirect was needed, which is
advice someone would have acted on. I corrected the same claim in config.go and
extension.go in the last commit and missed this file, so this is the grep I
should have run before committing:
grep -rn "prebuilt shell artifact|no renderer|nothing is mounted" \
extensions/dashboard --include='*.go'
One hit is left and it is accurate history, in shell_handlers.go, about the old
shell having been deleted.
Validate() now allow-lists shell_source the way it already allow-lists theme and
default_access. Empty stays legal and still means embedded. A typo used to match
neither branch of shellEnabled(), so `shell_source: embeded` cost you the whole
dashboard with nothing in the logs.
The static handler had two smaller problems. It tested the asset path with
strings.Contains, so /vendor/assets/x.js got a year of immutable caching without
a content hash to back the promise. That is HasPrefix now. And it set
Cache-Control before handing off to the file server, which decides the status
afterwards, so an error response could in principle inherit the immutable
directive. Go's http.Error happens to clear the header map first, so no 404 was
ever actually served that way, but the guarantee should not rest on that. The
wrapper applies the header on 2xx and on 304. The 304 case matters: a
revalidation response has to carry the freshness directives the 200 would have,
or a client that revalidates ends up worse off than one that never asked.
Also pinned IsPlaceholder() against the committed placeholder. Edit the meta tag
without editing the sentinel and the startup warning stops firing, and the only
symptom is a log line that is not there. The test skips when a real artifact is
unpacked over dist/, so release CI is unaffected.
Two doc corrections. The dist README had its opening sentence inverted, saying
everything except the two placeholder files was the placeholder. And
WithShellSource claimed ShellExternal left {BasePath}/ui unmounted; the routes
are not registered, but the path falls through to the ForgeUI catch-all at
{BasePath}/*. Measured: it still 404s today, through a different handler, and a
deployment that registers its own page there gets it.
The route-ordering comment now names the backends it was measured against and
admits the fourth. extras.httprouter panics at registration on these three
routes in either order rather than resolving them, so "every backend forge
ships" was an overclaim.
* ci(release): embed the pinned dashboard shell artifact
* feat(cli): forge dashboard new
* ci(release): fix non-atomic dist swap, exit-code leak, and guard status handling
- extract into a staging directory and swap it into dist/ rather than
clearing dist/ then extracting in place, so a failed extraction (disk
full, I/O error) never leaves dist/ empty
- the cleanup() EXIT trap could itself fail under set -e and silently
turn a successful run into exit 1; made every cleanup step best-effort
- the uncommitted-changes guard is capturing git status's output before
testing it, so a failing git status can no longer be misread as clean
- documented the residual gap where nothing distinguishes a real
goreleaser release from an ad hoc local one with a real token
* fix(cli): add PluginErrorBoundary and dashboard-runtime to dashboard scaffold
* fix(cli): anchor the boundary discriminator to structure, tidy dashboard plugin docs
* fix(dashboard): gate the shell pin behind a sentinel and verify its digest
The next forge release would have died in the before-hook.
.dashboard-shell-version said 0.0.0, no forge-dashboard release exists
under that tag, and the fetch script goes strict whenever CI=true, which
every GitHub runner sets. Both the release path and the dry-run rehearsal
fail there.
So the script knows a sentinel now. An empty version file, or the literal
"none", means no shell is pinned yet. That path keeps the placeholder and
exits 0 even under CI, and it warns loudly on the way out naming the
sentinel, because a release built that way serves a placeholder page and
ought to say so. Both files read "none" today. Cutting a real shell
release is the owner's action, so the bump procedure now lives in
extensions/dashboard/shellassets/dist/README.md: who tags what, in which
order, and what goes in each file.
The other half is integrity. A GitHub release asset can be deleted and
re-uploaded under an existing tag with no commit and no trace in git
history, so pinning a version pins a name and nothing about the bytes.
Anyone with write access to that repo's releases could put arbitrary
JavaScript into every Forge binary's dashboard.
.dashboard-shell-version.sha256 now sits beside the version pin, and the
download is checked against it before anything is unpacked. A mismatch is
fatal in every mode, strict or not, and a pinned version carrying no
digest is refused outright.
Also in here:
* The scaffold's @forge-go deps move from "^0.0.0" to "latest". Caret does
not widen on a 0.0.x, so "^0.0.0" means exactly 0.0.0, and every
scaffolded project failed its first install.
* `forge dashboard new` and the README it writes both say the packages are
not published yet. That was honest in the source comments and nowhere a
user would look.
* The fetch script strips and validates a leading "v", so a version file
reading "v1.4.0" no longer builds a URL under download/vv1.4.0/.
* .gitignore covers the script's staging and stale sibling directories.
* The Cache-Control comments blamed http.Error. The real mechanism is the
unexported net/http.serveError in fs.go, gated behind
GODEBUG=httpservecontentkeepheaders.
* embed.go says why the directive is a plain `dist`. Dropping the `all:`
is what keeps the fetch marker out of the binary.
* "Task 2", "fix round 1" and "fix round 2" are gone from shipped source.
* docs(dashboard): point the bootstrap comment at shellBase, not basePath
The doc comment on shellBootstrap still said the client reads basePath for
its router basename. It never did, and after this wave the basename is
shellBase. Two other comments in this file were corrected in the same round
and this one was missed, so it sat directly beside the fix it contradicts.
That is worse than a stale comment. basePath is the contract's prefix, one
directory above the pages, and a reader who follows the sentence wires
basename={basePath}, which matches nothing and leaves an empty content pane
under a sidebar and header that look perfectly healthy. It is the exact bug
the new App tests exist to catch, and substituting basePath for shellBase
fails both of them.
The correction names shellBase and then says plainly that basePath is not
the basename, because the field is two lines below and picking the wrong
one is the natural mistake.
* refactor(streaming): drop the server-rendered dashboard
The React plugin @forge-go/dashboard-plugin-streaming has been rendering
overview, rooms and connections since W5, and it reads everything from the
streaming-contract contributor. That leaves extensions/streaming/dashboard
serving nothing, so it goes.
Deleting the package takes two registration sites with it.
DashboardContributor built the templ LocalContributor, and
RegisterDashboardBridge registered ten streaming.* RPC functions on the
forgeui bridge. Both are optional interfaces that the dashboard extension
probes with a type assertion, so the assertions stop matching and discovery
skips streaming. RegisterContractContributor stays, and it is now the only
dashboard surface streaming exposes.
templ and forgeui are no longer direct dependencies. Both stay in go.mod as
indirect, because extensions/dashboard/contract pulls in
extensions/dashboard/auth, and that package still renders its own login
pages with templ and routes them through forgeui. Those two lines will clear
on their own once forge's own dashboard migrates off templ, which is the last
step of W6 and waits on authsome.
* feat(dashboard)!: remove the LegacyUI escape hatch and CoreContributor
LegacyUI gated exactly one thing: constructing CoreContributor and
registering it under "core" so the templ core pages (overview, health,
metrics, services, extensions, traces) had something to render through.
Default was already off, so this drops dead configuration rather than
changing behavior for anyone running the default.
Removes Config.LegacyUI, WithLegacyUI, PagesConfig.LegacyUI, and
core_contributor.go. WithLegacyUI was exported; nothing in this
monorepo called it (confirmed by the W6 GA legacy-surface-map), but
it is a breaking API change for anything outside it.
PagesManager.RegisterPages no longer branches on LegacyUI -- it always
takes the RootContributor path when one is set, same as before with
the flag off. registerLegacyCorePages and its ten page handlers stay
in pages.go, now unreachable: retiring the rest of the templ
contributor system is a separate decision under review, not this
task's job.
* docs(dashboard): repoint the slice f link at the contract package
extensions/streaming/dashboard is gone, so the link in the context section
resolved to nothing. It now names the deleted path in plain text and links to
extensions/streaming/contract, which is what took over.
The rest of the doc still reads as though the migration window is open. It
says the legacy /dashboard/ext/streaming/* URLs keep working, and it defers
retiring the templ paths to a follow-on slice. That slice is W6, and the
streaming half has landed, so the body needs a pass once the dashboard side
settles.
* feat(dashboard)!: delete the core server-rendered pages
Removes the ten templ page components (overview, health, metrics,
metrics/all, metrics collector detail, metrics detail, services,
extensions, traces, trace detail), the ten now-unreachable PagesManager
methods that rendered them, and registerLegacyCorePages, which used to
wire them onto routes. Their supporting *_helpers.go files and
chart_helpers.go go with them. 15,096 lines gone.
This is safe because nothing could reach any of it any more. Task 2
already removed LegacyUI and CoreContributor, the only thing that
constructed the "core" contributor these pages rendered through and the
only caller of registerLegacyCorePages, so the pages had been
unreachable since that commit. On the data side, core-contract already
serves an intent for everything these ten covered: Overview, Health,
Metrics, Traces, Services, and Extensions.
The templ contributor system stays, and that's deliberate, not
something this commit forgot. RegisterPages had exactly one
LegacyUI-gated branch, the one choosing between registerLegacyCorePages
and whatever RootContributor was set. Everything else it does, settings
pages, extension-layout contributor pages, remote contributor pages and
widgets, is live code with no contract-side replacement, and none of it
was ever behind the flag. Extensions can still contribute pages, widget
fragments, and settings panels through that system, and the shell
renders them through the fragment proxy.
Two pieces of the deleted pages' plumbing stay for the same reason.
ui/pages/error.templ (ErrorPage) and the bare ui package's
WidgetErrorFragment are called from live routes in pages.go: contributor
lookup failures, remote widget failures, settings errors. None of that
is CoreContributor-specific. And a-h/templ stays in go.mod because the
contributor system this commit leaves standing is templ end to end.
The old core paths (/, /health, /metrics, /services, /extensions,
/traces) 404 now, and nothing redirects them. The replacement is the
React shell at {BasePath}/ui, which the extension mounts itself.
* docs(dashboard): stop advertising the deleted core pages
features.mdx, index.mdx, the extension README, and the basic example
all described /, /health, /metrics, and /services as pages the built-in
core contributor serves. That contributor is gone, nothing configures
those routes back, and docs/content/docs is mirrored to the public site,
so this was about to ship four 404ing URLs in a GA release.
Replaced each with what's actually there: the React shell at
{BasePath}/ui, mounted directly by the extension, which covers those
same four views and reads its data over the contract envelope. The
legacy JSON API at {BasePath}/api/* is untouched and still documented
as is, since it's a deliberate, separate thing. Called out explicitly
in both docs and the README that the templ contributor system survives:
extensions still register pages, widgets, and settings through it, and
the shell renders them via the fragment proxy.
The basic example's header comment and inline URLs pointed at the same
dead routes. Updated it to print the working shell URL and describe
what it actually demonstrates now: default configuration, no
contributors registered.
* docs(dashboard): correct SLICE_I_DESIGN's rejected redirect plan
This design doc's "replace with redirects" section described a plan
that was reconsidered before implementation and never built: the ten
old core paths don't 302 anywhere, they 404, and pages.go says so in
its own comment. The doc still claimed 302s in three places: the
redirect table, a curl example in Verification, and a bookmarks claim
in Risks. All three would mislead anyone who read this after the fact
looking for a redirect that doesn't exist. The table's targets were
also wrong on their own terms: the shell moved to {BasePath}/ui, not
the /dashboard/contract/app/* path this doc assumed throughout.
Added a "What actually shipped" section up top pointing at the
correction, marked the redirect table as considered-but-rejected rather
than deleting it, and updated Verification and Risks to describe the
actual 404 behavior instead of a 302 that was never built. Fixed the
same stale URL in "Why now." Left the "Out of scope" keep list alone.
It's the one place in the repo that names the surviving templ pieces
(ui/shell, layouts, the bare ui components, error.templ, the settings
and extension handlers) accurately, and it's worth keeping findable.
* fix(dashboard): correct comments left describing deleted code
Four comments still described CoreContributor or its core pages as
present tense fact after both were removed.
pages.go's RegisterPages doc comment said core dashboard pages inherit
the default layout; there are no core dashboard pages, only the root
contributor page does that when one is configured. The line above
defaultMW said it resolved middleware "for core pages"; that middleware
is what settings, the root contributor, and remote contributor pages
actually use.
pilot.go's Deps doc said slice (h) added providers so the pilot covers
every page CoreContributor serves; reworded to say what survives
without the name, since the pilot's job (covering the same data the old
pages rendered) didn't change when the type went away.
extension.go had the same pattern in the pilot.Register call: a comment
about wiring "the rest of CoreContributor's data sources" now names the
data sources instead of the type that no longer exists.
Also corrected extension.go's other RebuildIndex call, which runs right
after e.registry is constructed and before anything registers into it.
Its comment claimed it rebuilds "against any contributors registered
later," which was true when CoreContributor's construction sat between
those two points; now nothing does, so the call indexes zero
contributors every time. Kept the call rather than removing it (this
wave is comment-only, no behavior changes) and rewrote the comment to
say why an empty rebuild here is harmless: RebuildIndex is a cheap,
idempotent scan, and the rebuild that actually matters runs later in
discoverExtensionContributors, after contributors exist to index.1 parent 4ee53c3 commit 4f29ed0
342 files changed
Lines changed: 3650 additions & 59287 deletions
File tree
- cmd
- dashboard-contract-probe
- forge
- plugins
- docs/content/docs/extensions/dashboard
- extensions
- dashboard
- contract
- components
- loader
- pilot
- server
- shell
- dist
- assets
- src
- auth
- components
- ui
- contract
- intents
- layout
- lib
- runtime
- test
- testdata
- transport
- examples/basic
- handlers
- pages
- shellassets
- dist
- ui/pages
- streaming
- contract
- dashboard
- scripts
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
76 | 96 | | |
77 | 97 | | |
78 | 98 | | |
| |||
123 | 143 | | |
124 | 144 | | |
125 | 145 | | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | 146 | | |
130 | 147 | | |
131 | 148 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
33 | 43 | | |
34 | 44 | | |
35 | 45 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
0 commit comments