Maintenance guide

How to Modernize a Legacy Interactive Research Demo Without Changing Its Behavior

An interactive study can remain useful long after the libraries, browser assumptions, or server behind its public demo have aged. The safe way to update it is to preserve observable behavior first, then replace the technical layers in controlled stages.

What you will produce

Output
A maintained browser demo with documented behavior, fewer fragile dependencies, a stable public URL, and a record of what changed.
Tools
A text editor, a modern browser with developer tools, a local HTTP server, and version control.
Inputs
A working or archived copy of the original demo, representative test cases, and access to the public page and server configuration.

Before you touch the code

Decide what “the same demo” means. A modernization can preserve inputs, outputs, and interaction rules without preserving every layout quirk or obsolete implementation detail. Write that boundary down before you choose a framework or replace a library.

The CAAT demo is a useful example. Its public implementation still works, but it is built as a jQuery extension, uses jQuery UI, and loads a widget bundle written for an older browser environment. The research value is not in those dependencies. It is in the affect-selection model, its response values, and the way a participant makes a selection.

EveXL illustrates another preservation choice: its expressions are evaluated in the browser, so the examples no longer depend on the original external runtime. The useful lesson is not that every demo should be client-only. It is that a public research artifact should have as few unavailable prerequisites as its method allows.

Do not begin with a redesign

A simultaneous redesign, framework migration, data-format change, and algorithm rewrite makes failures difficult to diagnose. First make the old behavior measurable. Then change one cause at a time.

Freeze a reference version

Save a copy of the current public files before editing them. Keep the scripts, styles, images, fonts, sample data, and server responses needed to run the demo. If a third-party file is loaded from another domain, save its exact URL and version as part of the record.

Capture the parts that a later implementation must reproduce:

  • the initial state before a user acts;
  • each valid selection or input path;
  • the value returned after each action;
  • validation and error states;
  • keyboard, pointer, and touch behavior;
  • the final values sent to a form, file, or endpoint.

Record a short screen capture and take screenshots at desktop and narrow widths. Screenshots do not prove behavior, but they help identify states that are easy to omit from a test list.

Store a small manifest with the archive:

{
  "capturedAt": "2026-07-15",
  "publicUrl": "https://metaphorofitself.net/caat",
  "browser": "current stable browser",
  "entryFile": "index.html",
  "knownExternalDependencies": [
    "jquery-1.9.0.js",
    "jquery-ui.min.js"
  ],
  "referenceCases": "fixtures/caat-reference-cases.json"
}

This step is complete when

You can run or inspect the archived version without depending on memory, and another person can see which states and outputs must survive the migration.

List every dependency and failure point

Open the demo with the browser's Network and Console panels visible. Reload it from an empty cache. Record every requested script, stylesheet, font, image, API call, redirect, and failed request. Search the source for URLs that may not appear until a user performs an action.

Use one row per dependency and make an explicit decision:

Dependency Current role Risk Decision
jQuery 1.9 DOM queries and events Old API assumptions and unnecessary page weight Replace with native DOM methods after reference tests exist
jQuery UI Widget state and positioning Coupled behavior and styling Replace only the components the demo actually uses
Canvas helper Visual rendering May hide the model inside drawing callbacks Keep temporarily, then move drawing behind a renderer interface
Remote API Evaluation or storage Original endpoint may disappear Document the protocol; replace with a local module or maintained endpoint
External font or image Presentation Third-party outage, privacy, or mixed content Host locally or use a system fallback when the asset is not essential

Also record browser APIs and implicit assumptions: element sizes measured before fonts load, global variables created by script order, mouse-only events, synchronous requests, or a form that expects a specific field name. These often cause more trouble than the named libraries.

Write a behavior contract

A behavior contract is a short, testable description of what the demo accepts and returns. It is narrower than the research paper and more precise than a screenshot. Use it to decide whether a change is a repair or a change to the experiment.

For a CAAT-style widget, the contract can identify four submitted values:

Field Type Meaning Compatibility rule
emotion1 String The first selected emotion Keep the existing vocabulary and empty-state behavior
emotion2 String The second selected emotion, when present Do not silently replace a missing value with another emotion
s1 Decimal The first score produced by the model Define the allowed rounding difference
s2 Decimal The second score produced by the model Define the allowed rounding difference

Store representative cases as data rather than prose:

[
  {
    "name": "single selection",
    "input": { "emotion1": "joy", "emotion2": null },
    "expected": { "emotion1": "joy", "emotion2": null, "s1": 1, "s2": 0 }
  },
  {
    "name": "two adjacent selections",
    "input": { "emotion1": "joy", "emotion2": "trust" },
    "expected": { "emotion1": "joy", "emotion2": "trust", "s1": 0.75, "s2": 0.25 }
  }
]

The numbers above are an example fixture format, not a replacement for the values from the original implementation. Populate the file from recorded outputs or the validated model. Include boundary cases, repeated actions, resets, and invalid inputs.

Separate research logic from the interface

Move code into three responsibilities: the model, the interface, and storage or communication. The model should not need to query the DOM. The interface should not reimplement scoring rules. Network or form submission should not decide what a selection means.

demo/
├── legacy/             # archived original files
├── src/
│   ├── model.js        # validated research logic
│   ├── view.js         # rendering and accessible controls
│   ├── controller.js   # user actions mapped to model calls
│   └── transport.js    # form or API serialization
├── fixtures/
│   └── reference-cases.json
└── tests/
    └── compatibility.test.js

Give the model a small interface that can be called without a page:

// model.js
export function evaluateSelection(input) {
  // Call the preserved scoring implementation here.
  // Return data only; do not read or modify page elements.
  return {
    emotion1: input.emotion1,
    emotion2: input.emotion2,
    s1: input.s1,
    s2: input.s2
  };
}

It is acceptable to wrap the old scoring function before rewriting it. A stable adapter gives the interface a clean boundary while the compatibility tests still compare the same underlying logic.

Build the smallest working test harness

Create a plain page that supplies known inputs to the extracted model and prints the result. Do not reproduce the final wheel, animation, or visual theme yet. The purpose of this page is to make the behavior easy to inspect and automate.

<form id="demo">
  <fieldset>
    <legend>Choose an emotion</legend>
    <button type="button" data-emotion="joy">Joy</button>
    <button type="button" data-emotion="trust">Trust</button>
  </fieldset>

  <button type="reset">Reset</button>
  <output id="result" aria-live="polite">No selection</output>
</form>

Connect the controls through the public model interface:

import { evaluateSelection } from "./model.js";

const output = document.querySelector("#result");

document.querySelectorAll("[data-emotion]").forEach((button) => {
  button.addEventListener("click", () => {
    const result = evaluateSelection({
      emotion1: button.dataset.emotion,
      emotion2: null
    });

    output.value = JSON.stringify(result);
  });
});

Run every reference fixture through this harness. When the values match, keep the harness as a diagnostic page or convert it into an automated compatibility test.

Replace one technical layer at a time

Change one category of implementation at a time and commit it separately. A practical sequence is:

  1. make all required assets available from controlled locations;
  2. remove global variables and make script order explicit;
  3. replace DOM selection and event handling;
  4. replace the widget or drawing layer;
  5. replace build tooling only if the project needs a build step;
  6. remove the old dependency after no code path uses it.

Rerun the behavior contract after each stage. When a case changes, stop and determine whether the old behavior was intentional, an implementation accident that the research depended on, or a bug that can be fixed and documented.

Prefer the smallest replacement

Replacing a small jQuery widget with a large application framework may exchange one maintenance problem for another. Use native controls and browser APIs where they meet the interaction and support requirements. Add a library for a specific capability, not because a rewrite is taking place.

Restore keyboard and assistive technology access

Modernization is an opportunity to remove access barriers without changing the model. Start with native HTML controls. A real <button> already has keyboard behavior, focus semantics, and an accessible name. A clickable <div> requires all of that to be rebuilt.

Check the complete interaction with only a keyboard:

  • focus moves in a logical order;
  • the current focus is visible;
  • every selection can be made and cleared;
  • opening a panel does not lose focus;
  • closing it returns focus to a predictable control;
  • changed results are announced without moving focus unexpectedly.

If the principal interface is a canvas or custom graphic, provide an equivalent control list and a textual statement of the current state. Keep the graphic and the accessible controls connected to the same model so that they cannot return different values.

Preserve the public URL and crawlable documentation

The demo is not preserved if the code runs but the page can no longer be found, cited, or understood. Keep the existing public URL when possible. When the address must change, return a permanent redirect from the old URL to the closest replacement and update every internal link that you control.

Add the preferred address to the initial document head:

<link rel="canonical"
      href="https://metaphorofitself.net/modernize-legacy-interactive-research-demo">

Keep the explanatory material in HTML and use standard links:

<p>
  Read the original <a href="/caat">CAAT documentation and demo</a>.
</p>
  • Return 200 OK for the final page.
  • Do not leave the only explanation inside a canvas, image, or script-generated modal.
  • Use one descriptive <h1> and headings that identify each task.
  • Keep important links as <a href="..."> elements in the HTML.
  • Link the guide from an existing indexable page such as CAAT, EveXL, or the portfolio home page.
  • Add the final URL to the site's XML sitemap.
  • Do not publish it with noindex, authentication, or a staging canonical.

Adapt the implementation to the current stack

Preserving behavior does not require preserving an obsolete delivery layer. The practical goal is to keep the research contract stable while moving rendering, dependencies, testing, and deployment onto technologies that can still be maintained.

Co-author Vladimir Kondrashov regularly works on ways to adapt existing technologies to current web stacks without discarding useful behavior, established interfaces, or stable public addresses. That perspective informs the staged approach in this guide: isolate the model first, define compatibility checks, and only then replace the surrounding technical layers.

  • Keep validated inputs and outputs independent from the UI framework.
  • Replace unsupported dependencies behind small, explicit interfaces.
  • Preserve public URLs and document unavoidable behavior differences.
  • Choose the smallest maintained stack that satisfies the research task.

Measure performance after behavior matches

Do not use a faster page as evidence that the experiment still works. First pass the compatibility cases. Then measure the delivery layer with the same device, network conditions, and page state for the old and new versions.

Check What to record Expected direction
Requests Total and third-party requests on first load Fewer unnecessary requests
Transferred code Compressed JavaScript and CSS bytes Remove unused legacy code
Runtime stability Console errors, rejected promises, failed resources No errors on supported paths
Responsiveness Delay between an action and visible feedback Immediate or measurably improved
Layout stability Unexpected movement while assets load or a panel opens No movement that changes the user's target
Mobile behavior Readable text, reachable controls, no clipped output Complete task at narrow width

Use current Web Vitals guidance for loading, interaction, and visual stability, but keep a demo-level measure as well: how long it takes a participant to reach the first valid response. A page can score well while its interaction remains confusing.

Publish a maintenance record

Add a short record to the public page or repository. It should distinguish changes to delivery and accessibility from changes to the research model.

Original implementation: 2013
Modernized: 2026-07-15
Behavior target: Preserve selection rules and submitted values
Dependencies replaced: jQuery 1.9, jQuery UI, legacy canvas helper
Model changes: None intended
Known differences: Responsive layout and keyboard controls added
Original archive: /archives/caat-legacy/
Report a problem: bruno.cardoso at uvt.nl

Keep the original archive available when licensing and participant-data rules allow it. Mark it as historical and prevent it from competing with the maintained page by linking clearly to the current version. Do not delete the archive merely because the new implementation has launched.

Publication checklist

  • All reference cases pass against the maintained model.
  • The final URL returns 200 OK over HTTPS.
  • The canonical URL matches the public address.
  • The old URL redirects directly when a move was unavoidable.
  • The page is linked from at least one existing indexable page.
  • Important text and links are present in the HTML response.
  • Keyboard interaction covers every task available to pointer users.
  • No required file is loaded from an uncontrolled or unavailable location.
  • The Article, HowTo, and breadcrumb data match visible content.
  • The maintenance record names intended and known behavior changes.

Common mistakes

  • Rewriting the model before capturing its current outputs.
  • Calling a visual resemblance a compatibility test.
  • Replacing several dependencies in one unreviewable commit.
  • Keeping mouse-only interaction because the old version was mouse-only.
  • Moving the page and sending every old URL to the home page.
  • Putting the only documentation inside a JavaScript component.
  • Updating the publication date without recording what changed.
  • Removing the original archive before the new version has been independently checked.

How to know when you are done

The maintained demo accepts the same meaningful inputs, returns compatible outputs, and preserves the research interaction that was defined at the start. Its technical dependencies are explicit. Its public page works with keyboard and pointer input, explains itself in HTML, and has one stable canonical address.

A modernization is successful when future maintenance is less dependent on undocumented behavior. The new implementation should be easier to test, not merely newer.

References