migration-work

v3v4 · +447 −150 · View latest

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="A chronological record of Unravel migration work across product, cloud, data, SDK, and backend systems.">
  <title>Migration Control Map | Unravel</title>
  <title>Migration Work (Confidential) | Unravel</title>
  <style>
    :root {
      --paper: #f4f6f9;
⋯ 242 unchanged lines
      position: relative;
      display: grid;
      grid-template-columns: clamp(6.4rem, 13vw, 11rem) 3rem minmax(0, 1fr);
      min-height: 30rem;
      min-height: 0;
      border-top: 1px solid var(--line);
      align-items: start;
    }
    .migration-case:last-child {
⋯ 28 unchanged lines
      transform: translateX(-50%);
    }
    .case-content {
      padding: clamp(2rem, 4vw, 3.5rem) 0 clamp(4rem, 8vw, 7rem) clamp(1rem, 4vw, 4rem);
    .case-disclosure {
      grid-column: 3;
      min-width: 0;
      margin: 0;
      border: 0;
      padding: 0;
    }
    .case-header {
    .case-summary {
      position: relative;
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      gap: 1.5rem;
      align-items: start;
      width: 100%;
      min-height: 44px;
      margin: 0;
      padding: clamp(2rem, 4vw, 3.25rem) clamp(2.25rem, 4vw, 4rem) clamp(2rem, 4vw, 3.25rem) clamp(1rem, 4vw, 4rem);
      gap: 0.75rem;
      cursor: pointer;
      list-style: none;
    }
    .case-header h3 {
    .case-summary::-webkit-details-marker {
      display: none;
    }
    .case-summary::marker {
      display: none;
    }
    .case-summary::after {
      content: "+";
      position: absolute;
      top: clamp(2rem, 4vw, 3.25rem);
      right: 0.25rem;
      display: grid;
      width: 2rem;
      height: 2rem;
      place-items: center;
      color: var(--signal-deep);
      font-family: var(--display);
      font-size: 1.7rem;
      line-height: 1;
      transform: rotate(0deg);
      transition: transform 260ms var(--ease-out);
      pointer-events: none;
    }
    .case-disclosure[open] > .case-summary::after {
      transform: rotate(45deg);
    }
    .case-summary:focus-visible {
      outline: 3px solid var(--focus);
      outline-offset: 4px;
      border-radius: 0.3rem;
    }
    .case-title {
      display: block;
      max-width: 18ch;
      margin: 0;
      font-family: var(--display);
      font-size: clamp(2.4rem, 5vw, 5.2rem);
      font-weight: 600;
      letter-spacing: -0.025em;
      line-height: 0.92;
      text-wrap: balance;
    }
    .case-preview {
      display: block;
      margin: 0;
      max-width: 45ch;
      color: var(--ink-soft);
      font-weight: 600;
      line-height: 1.48;
    }
    .case-body {
      overflow: hidden;
      opacity: 0;
      transform: translateY(-0.5rem);
      transition: opacity 240ms var(--ease-out), transform 320ms var(--ease-out);
    }
    .case-disclosure[open] .case-body {
      opacity: 1;
      transform: translateY(0);
    }
    .case-content {
      padding: 0 0 clamp(4rem, 8vw, 7rem) clamp(1rem, 4vw, 4rem);
    }
    .case-header {
      display: flex;
      justify-content: flex-start;
      align-items: center;
      gap: 1.5rem;
      margin-bottom: 0.35rem;
    }
    .timeline-controls {
      display: flex;
      justify-content: center;
      margin-top: clamp(1.5rem, 4vw, 3rem);
    }
    .timeline-toggle {
      display: inline-flex;
      min-height: 44px;
      align-items: center;
      justify-content: center;
      padding: 0.75rem 1.25rem;
      border: 1px solid var(--ink);
      border-radius: 999px;
      background: var(--ink);
      color: var(--paper-bright);
      font: inherit;
      font-weight: 700;
      cursor: pointer;
      transition: background-color 180ms var(--ease-out), color 180ms var(--ease-out), transform 180ms var(--ease-out);
    }
    .timeline-toggle:hover {
      background: var(--signal-deep);
      transform: translateY(-2px);
    }
    .timeline-toggle:focus-visible {
      outline: 3px solid var(--focus);
      outline-offset: 4px;
    }
    .timeline-toggle[hidden] {
      display: none;
    }
    .confidentiality-notice {
      display: grid;
      width: min(calc(100% - 2rem), 83rem);
      margin: clamp(1rem, 2vw, 1.5rem) auto 0;
      padding: 1rem clamp(1rem, 3vw, 1.5rem);
      border: 1px solid var(--line);
      border-left: 0.35rem solid var(--signal);
      border-radius: 0.35rem;
      background: var(--signal-wash);
      color: var(--ink);
      font-size: 0.95rem;
      line-height: 1.45;
    }
    .confidentiality-notice strong {
      font-family: var(--display);
      font-size: 1.05rem;
    }
    .case-status {
      display: inline-flex;
      align-items: center;
⋯ 80 unchanged lines
      margin-top: 0.35rem;
    }
    .case-ai {
    .case-ai,
    .case-judgment,
    .case-source {
      margin-top: 1.75rem;
      font-weight: 700;
    }
    .case-ai span {
    .case-judgment,
    .case-source {
      max-width: 75ch;
      color: var(--ink-soft);
      font-weight: 400;
    }
    .case-ai span,
    .case-judgment span,
    .case-source span {
      display: inline-block;
      margin-right: 0.45rem;
      font-family: var(--display);
⋯ 247 unchanged lines
        top: 2.25rem;
      }
      .case-content {
      .case-disclosure {
        grid-column: 2;
        padding: 1rem 0 4rem 1rem;
      }
      .case-summary {
        padding: 1rem 2.5rem 2rem 1rem;
      }
      .case-summary::after {
        top: 1rem;
      }
      .case-content {
        padding: 0 0 4rem 1rem;
      }
      .case-header,
      .case-metrics,
      .case-facts {
⋯ 71 unchanged lines
  </header>
  <main id="main-content">
    <aside class="confidentiality-notice" data-confidentiality-notice>
      <strong>For your eyes only</strong>
      <span>Confidential working document. OneStudyTeam has approved public use of its name and reference; supporting operational details remain private. Share only with intended recipients through controlled Thing access.</span>
    </aside>
    <section class="simple-hero reveal" id="top" aria-labelledby="hero-title">
      <div class="hero-inner">
        <h1 id="hero-title">
⋯ 26 unchanged lines
        <p>Each entry leads with business impact, elapsed duration, and a complexity rating with reasons. Technical details follow only to explain how value and continuity were protected.</p>
      </header>
      <ol class="route-list">
        <li class="migration-case reveal" data-case="celerity-java-to-python" data-evidence="confirmed" data-sort="2026.5">
      <ol class="route-list" id="migration-timeline">
        <li class="migration-case reveal" data-case="celerity-java-to-python" data-evidence="confirmed" data-sort="2026.5" data-timeline-priority="true">
          <div class="case-year">2026</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Celerity<br>Java → Python backend</h3>
              <span class="case-status">Unreleased target</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Removed vendor lock-in, returned direct product change control, reduced source maintenance surface 55%, and created 4 independently deployable and scalable processes.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>4 active weeks. Original Java build took about 21 active weeks. Two more weeks would have avoided overtime.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Two strict mobile contracts, Kafka compatibility, hidden framework behavior, data and storage invariants, and a compressed finish.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>Java Quarkus monolith to Python FastAPI API, consumer, renderer, and dispatcher.</p></section>
              <section><h4>The constraint</h4><p>Live iOS and Android clients defined compatibility, but Python target was unreleased. This was not a live production cutover.</p></section>
              <section><h4>The method</h4><ul><li>Extract contracts from mobile clients.</li><li>Verify API, database, and storage behavior against real stack.</li><li>Use 101 client contract assertions and 247 live-stack checks per gate.</li></ul></section>
          <details class="case-disclosure" open>
            <summary class="case-summary" aria-controls="celerity-java-to-python-details">
              <span class="case-title" role="heading" aria-level="3">Celerity<br>Java → Python backend</span>
              <span class="case-preview">Returned direct product change control and reduced maintenance surface by 55%.</span>
            </summary>
            <div class="case-body case-content" id="celerity-java-to-python-details">
              <header class="case-header">
                <span class="case-status">Unreleased target</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Removed vendor lock-in, returned direct product change control, reduced source maintenance surface 55%, and created 4 independently deployable and scalable processes.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>4 active weeks. Original Java build took about 21 active weeks. Two more weeks would have avoided overtime.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Two strict mobile contracts, Kafka compatibility, hidden framework behavior, data and storage invariants, and a compressed finish.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>Java Quarkus monolith to Python FastAPI API, consumer, renderer, and dispatcher.</p></section>
                <section><h4>The constraint</h4><p>Live iOS and Android clients defined compatibility, but Python target was unreleased. This was not a live production cutover.</p></section>
                <section><h4>The method</h4><ul><li>Extract contracts from mobile clients.</li><li>Verify API, database, and storage behavior against real stack.</li><li>Use 101 client contract assertions and 247 live-stack checks per gate.</li></ul></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>Yes. Claude Code agents did substantial build and verification work; humans defined contracts, gates, and architectural boundaries.</p>
              <a class="deep-dive" data-deep-dive href="https://thing.unravel.tech/kapil/terra-incognita-before-you-migrate" rel="noopener">Read Terra Incognita, the detailed migration account →</a>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>Yes. Claude Code agents did substantial build and verification work; humans defined contracts, gates, and architectural boundaries.</p>
            <a class="deep-dive" data-deep-dive href="https://thing.unravel.tech/kapil/terra-incognita-before-you-migrate" rel="noopener">Read Terra Incognita, the detailed migration account →</a>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="reify-retool-to-react-and-clojure" data-evidence="confirmed" data-sort="2026">
          <div class="case-year">2025<br>2026</div>
        <li class="migration-case reveal" data-case="reify-retool-to-react-and-clojure" data-evidence="confirmed" data-sort="2025.5" data-timeline-priority="true">
          <div class="case-year">2025</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Reify<br>Retool → React + Clojure</h3>
              <span class="case-status">Internal operations</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Higher reliability and lower operating cost for internal workflows.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>Under 3 months elapsed.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Live staff operations, integrations and data contracts, and multi-team coordination.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>Internal operations workflows moved from Retool to a custom React and Clojure implementation.</p></section>
              <section><h4>The constraint</h4><p>Keep live staff operations working across integrations and data dependencies.</p></section>
              <section><h4>The method</h4><p>Move workflow by workflow from Retool into the custom system.</p></section>
          <details class="case-disclosure" open>
            <summary class="case-summary" aria-controls="reify-retool-to-react-and-clojure-details">
              <span class="case-title" role="heading" aria-level="3">OneStudyTeam<br>Retool → React + Clojure</span>
              <span class="case-preview">$80K annual savings, with no downtime, no outage, and no loss of customizations.</span>
            </summary>
            <div class="case-body case-content" id="reify-retool-to-react-and-clojure-details">
              <header class="case-header">
                <span class="case-status">B2B healthcare SaaS</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>$80K saved per year, zero downtime, and no outage with no loss of customizations. Customer UX improved while the migrated stack stayed maintainable by the engineering team.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>April through June 2025, under 3 months elapsed. 2 senior engineers focused on AI-assisted migration delivery.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>Medium. Integrations and data contracts, relational schema with 2,000 columns, data migration, and multi-team/stakeholder coordination. Warehouse redesign was included for performance under human judgment.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p><a href="https://www.onestudyteam.com/" data-case-reference="onestudyteam">OneStudyTeam</a> used Retool for weekly customer onboarding, updates, customization handoff, and sales/customer-success upselling across teams. The system contained more than 30,000 lines with a large custom workflow surface.</p></section>
                <section><h4>The constraint</h4><p>The original developer had left, the system was unmaintained, customizations were frequently lost, and customer NPS dropped. A previous replacement effort exposed complex database migration and coordination across multiple teams and owners.</p></section>
                <section><h4>The method</h4><ul><li>Move one Retool workflow at a time after interviews and boundary definition with users and owners.</li><li>Scope backend, frontend, and data changes together and redesign when needed (including warehouse performance changes).</li><li>Deploy every workflow to test, collect feedback, iterate, then ship to production and retire the migrated Retool workflow.</li></ul></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>Yes. AI analyzed the 2,000-column relational schema and existing codebase, then generated synthetic data and property-based tests to fuzz data combinations.</p>
              <p class="case-judgment"><span>Human judgment</span>2 senior engineers owned workflow sequencing, integration and data-contract boundaries, continuity gates, cross-team decisions, stakeholder alignment, and a warehouse redesign to improve performance.</p>
              <p class="case-source"><span>Reference</span><a href="https://www.linkedin.com/in/radlakha/" data-case-reference="raman-adlakha" rel="noopener">Raman Adlakha, India Head and Director of Engineering, OneStudyTeam</a></p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>Yes.</p>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="healthtech-angular-to-react-checkout" data-evidence="confirmed" data-sort="2024">
        <li class="migration-case reveal" data-case="healthtech-angular-to-react-checkout" data-evidence="confirmed" data-sort="2024" data-timeline-priority="true">
          <div class="case-year">2024</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>HealthTech<br>Angular checkout → React</h3>
              <span class="case-status">Anonymous client</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Checkout conversion increased 6 to 8%. Disruptions caused by navigation or payment-window closure fell 30%.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 3 months elapsed.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>Medium. Revenue-critical flow, navigation and payment state risk, bounded frontend scope, and one cutover.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>Angular checkout to React, with checkout reliability improvements delivered during migration.</p></section>
              <section><h4>The constraint</h4><p>Protect customer progress and transaction completion through refreshes, back navigation, and payment-window closure.</p></section>
              <section><h4>The method</h4><ul><li>Persist early checkout state in local storage.</li><li>Use a reopenable payment popup.</li><li>Release through one controlled cutover.</li></ul></section>
          <details class="case-disclosure" open>
            <summary class="case-summary" aria-controls="healthtech-angular-to-react-checkout-details">
              <span class="case-title" role="heading" aria-level="3">HealthTech<br>Angular checkout → React</span>
              <span class="case-preview">Checkout conversion increased 6 to 8%, and disruption from navigation or payment windows fell 30%.</span>
            </summary>
            <div class="case-body case-content" id="healthtech-angular-to-react-checkout-details">
              <header class="case-header">
                <span class="case-status">Anonymous client</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Checkout conversion increased 6 to 8%. Disruptions caused by navigation or payment-window closure fell 30%.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 3 months elapsed.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>Medium. Revenue-critical flow, navigation and payment state risk, bounded frontend scope, and one cutover.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>Angular checkout to React, with checkout reliability improvements delivered during migration.</p></section>
                <section><h4>The constraint</h4><p>Protect customer progress and transaction completion through refreshes, back navigation, and payment-window closure.</p></section>
                <section><h4>The method</h4><ul><li>Persist early checkout state in local storage.</li><li>Use a reopenable payment popup.</li><li>Release through one controlled cutover.</li></ul></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>Yes.</p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>Yes.</p>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="helpshift-hadoop-hive-to-spark-redshift" data-evidence="confirmed" data-sort="2022.5">
          <div class="case-year">2022</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Helpshift<br>Hadoop + Hive → Spark + Redshift</h3>
              <span class="case-status">Live data platform</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Supported 10x platform growth, improved data accuracy from 90% to 99.99% in one year, and strengthened resiliency, scalability, and adoption.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 6 months elapsed. The 1-year accuracy period is an outcome window, not migration duration.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. 2 billion devices, 1 PB daily ingestion, live accuracy requirements, and a broad warehouse change.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>Hadoop and Hive to Spark and Redshift. Supporting stack included Hudi, PostgreSQL, Arrow, Airflow, and DBT.</p></section>
              <section><h4>The constraint</h4><p>Maintain processing continuity and data correctness at petabyte daily scale.</p></section>
              <section><h4>The method</h4><p>Modernize processing and warehouse architecture around Spark and Redshift, supported by Hudi, PostgreSQL, Arrow, Airflow, and DBT.</p></section>
          <details class="case-disclosure">
            <summary class="case-summary" aria-controls="helpshift-hadoop-hive-to-spark-redshift-details">
              <span class="case-title" role="heading" aria-level="3">Helpshift<br>Hadoop + Hive → Spark + Redshift</span>
              <span class="case-preview">Supported 10x platform growth and improved data accuracy from 90% to 99.99% in one year.</span>
            </summary>
            <div class="case-body case-content" id="helpshift-hadoop-hive-to-spark-redshift-details">
              <header class="case-header">
                <span class="case-status">Live data platform</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Supported 10x platform growth, improved data accuracy from 90% to 99.99% in one year, and strengthened resiliency, scalability, and adoption.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 6 months elapsed. The 1-year accuracy period is an outcome window, not migration duration.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. 2 billion devices, 1 PB daily ingestion, live accuracy requirements, and a broad warehouse change.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>Hadoop and Hive to Spark and Redshift. Supporting stack included Hudi, PostgreSQL, Arrow, Airflow, and DBT.</p></section>
                <section><h4>The constraint</h4><p>Maintain processing continuity and data correctness at petabyte daily scale.</p></section>
                <section><h4>The method</h4><p>Modernize processing and warehouse architecture around Spark and Redshift, supported by Hudi, PostgreSQL, Arrow, Airflow, and DBT.</p></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>No.</p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>No.</p>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="helpshift-mongodb-to-yugabyte" data-evidence="confirmed" data-sort="2022">
          <div class="case-year">2022</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Helpshift<br>MongoDB → YugabyteDB</h3>
              <span class="case-status">Zero downtime</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>More consistent data operations, greater scalability, and headroom for future load without interrupting customer service.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 6 months elapsed.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Live 2 TB store, data integrity, dual reads and writes, and a 20-plus phase roadmap.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>MongoDB primary store to YugabyteDB distributed SQL, with supporting service extraction.</p></section>
              <section><h4>The constraint</h4><p>Preserve data integrity and customer availability throughout cutover.</p></section>
              <section><h4>The method</h4><ul><li>Build staging environment.</li><li>Run both databases through dual reads and writes.</li><li>Move gradually across more than 20 phases.</li></ul></section>
          <details class="case-disclosure">
            <summary class="case-summary" aria-controls="helpshift-mongodb-to-yugabyte-details">
              <span class="case-title" role="heading" aria-level="3">Helpshift<br>MongoDB → YugabyteDB</span>
              <span class="case-preview">More consistent data operations, greater scalability, and headroom for future load.</span>
            </summary>
            <div class="case-body case-content" id="helpshift-mongodb-to-yugabyte-details">
              <header class="case-header">
                <span class="case-status">Zero downtime</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>More consistent data operations, greater scalability, and headroom for future load without interrupting customer service.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 6 months elapsed.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Live 2 TB store, data integrity, dual reads and writes, and a 20-plus phase roadmap.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>MongoDB primary store to YugabyteDB distributed SQL, with supporting service extraction.</p></section>
                <section><h4>The constraint</h4><p>Preserve data integrity and customer availability throughout cutover.</p></section>
                <section><h4>The method</h4><ul><li>Build staging environment.</li><li>Run both databases through dual reads and writes.</li><li>Move gradually across more than 20 phases.</li></ul></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>No.</p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>No.</p>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="helpshift-native-sdks-to-js" data-evidence="confirmed" data-sort="2021.5">
          <div class="case-year">2021<br>2022</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Helpshift<br>Native SDKs → shared JS core</h3>
              <span class="case-status">Customer-paced rollout</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Faster SDK delivery, consistent behavior across platforms, lower duplicated maintenance cost, and broader customer upgrade reach.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>4 to 6 months elapsed.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Multi-platform compatibility, version support, customer-controlled upgrades, and coordinated releases.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>Separate iOS, Android, and Unity or game-engine SDK implementations to a shared JavaScript core.</p></section>
              <section><h4>The constraint</h4><p>Keep existing native versions supported while customers choose their upgrade timing.</p></section>
              <section><h4>The method</h4><p>Validate shared behavior across all three platform families, then release through versioned opt-in.</p></section>
          <details class="case-disclosure">
            <summary class="case-summary" aria-controls="helpshift-native-sdks-to-js-details">
              <span class="case-title" role="heading" aria-level="3">Helpshift<br>Native SDKs → shared JS core</span>
              <span class="case-preview">Faster SDK delivery, consistent behavior across platforms, and lower duplicated maintenance cost.</span>
            </summary>
            <div class="case-body case-content" id="helpshift-native-sdks-to-js-details">
              <header class="case-header">
                <span class="case-status">Customer-paced rollout</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Faster SDK delivery, consistent behavior across platforms, lower duplicated maintenance cost, and broader customer upgrade reach.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>4 to 6 months elapsed.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Multi-platform compatibility, version support, customer-controlled upgrades, and coordinated releases.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>Separate iOS, Android, and Unity or game-engine SDK implementations to a shared JavaScript core.</p></section>
                <section><h4>The constraint</h4><p>Keep existing native versions supported while customers choose their upgrade timing.</p></section>
                <section><h4>The method</h4><p>Validate shared behavior across all three platform families, then release through versioned opt-in.</p></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>No.</p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>No.</p>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="helpshift-aws-california-to-virginia" data-evidence="confirmed" data-sort="2021">
        <li class="migration-case reveal" data-case="helpshift-aws-california-to-virginia" data-evidence="confirmed" data-sort="2021" data-timeline-priority="true">
          <div class="case-year">2021</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Helpshift<br>AWS California → Virginia</h3>
              <span class="case-status">Zero downtime</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Almost $1M in annual infrastructure savings. Production cutover completed with zero downtime and no data corruption, while establishing a more current operating baseline.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>9 months, January through October 2021, from active planning through production rollout. California decommissioning continued afterward.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. About 1,000 instances, 120 application services, 60K requests per second, 1 PB of S3 data, 150 TB of Kylin data, and multiple state and traffic strategies.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>AWS Northern California to Northern Virginia, including state, applications, traffic, release, monitoring, and infrastructure baselines.</p></section>
              <section><h4>The constraint</h4><p>Move a B2B customer portfolio without interruption, including a long tail and very large customers.</p></section>
              <section><h4>The method</h4><ul><li>Map service dependencies, readers, writers, and data entities.</li><li>Establish replicated event and state paths.</li><li>Move traffic by customer cohort, from test domains to top accounts.</li></ul></section>
          <details class="case-disclosure" open>
            <summary class="case-summary" aria-controls="helpshift-aws-california-to-virginia-details">
              <span class="case-title" role="heading" aria-level="3">Helpshift<br>AWS California → Virginia</span>
              <span class="case-preview">Almost $1M annual infrastructure savings, completed with zero downtime and no data corruption.</span>
            </summary>
            <div class="case-body case-content" id="helpshift-aws-california-to-virginia-details">
              <header class="case-header">
                <span class="case-status">Zero downtime</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Almost $1M in annual infrastructure savings. Production cutover completed with zero downtime and no data corruption, while establishing a more current operating baseline.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>9 months, January through October 2021, from active planning through production rollout. California decommissioning continued afterward.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. About 1,000 instances, 120 application services, 60K requests per second, 1 PB of S3 data, 150 TB of Kylin data, and multiple state and traffic strategies.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>AWS Northern California to Northern Virginia, including state, applications, traffic, release, monitoring, and infrastructure baselines.</p></section>
                <section><h4>The constraint</h4><p>Move a B2B customer portfolio without interruption, including a long tail and very large customers.</p></section>
                <section><h4>The method</h4><ul><li>Map service dependencies, readers, writers, and data entities.</li><li>Establish replicated event and state paths.</li><li>Move traffic by customer cohort, from test domains to top accounts.</li></ul></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>No.</p>
              <a class="deep-dive" data-case-deep-dive="aws-virginia" href="https://thing.unravel.tech/unravel/aws-virginia-migration">Read the full AWS migration deep dive →</a>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>No.</p>
            <a class="deep-dive" data-case-deep-dive="aws-virginia" href="https://thing.unravel.tech/unravel/aws-virginia-migration">Read the full AWS migration deep dive →</a>
          </article>
          </details>
        </li>
        <li class="migration-case reveal" data-case="helpshift-yui-to-react" data-evidence="confirmed" data-sort="2017">
          <div class="case-year">2015<br>2017</div>
          <div class="case-node" aria-hidden="true"></div>
          <article class="case-content">
            <header class="case-header">
              <h3>Helpshift<br>YUI → React</h3>
              <span class="case-status">Live system</span>
            </header>
            <div class="case-metrics">
              <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Product roadmap never paused. React improved future delivery, performance, reliability, consistency, usability, and extensibility.</p>
              <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 2 years elapsed.</p>
              <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Live product, broad UI surface, long coexistence, and uninterrupted feature work.</p>
            </div>
            <div class="case-facts">
              <section><h4>The change</h4><p>YUI frontend to React.</p></section>
              <section><h4>The constraint</h4><p>Keep shipping product features throughout migration.</p></section>
              <section><h4>The method</h4><p>Move incrementally by product surface while YUI and React coexist.</p></section>
          <details class="case-disclosure">
            <summary class="case-summary" aria-controls="helpshift-yui-to-react-details">
              <span class="case-title" role="heading" aria-level="3">Helpshift<br>YUI → React</span>
              <span class="case-preview">Product roadmap never paused while React improved future delivery and stability.</span>
            </summary>
            <div class="case-body case-content" id="helpshift-yui-to-react-details">
              <header class="case-header">
                <span class="case-status">Live system</span>
              </header>
              <div class="case-metrics">
                <p class="metric" data-field="impact"><span class="metric-label">Business impact</span>Product roadmap never paused. React improved future delivery, performance, reliability, consistency, usability, and extensibility.</p>
                <p class="metric" data-metric="duration"><span class="metric-label">Duration</span>About 2 years elapsed.</p>
                <p class="metric" data-metric="complexity"><span class="metric-label">Complexity</span>High. Live product, broad UI surface, long coexistence, and uninterrupted feature work.</p>
              </div>
              <div class="case-facts">
                <section><h4>The change</h4><p>YUI frontend to React.</p></section>
                <section><h4>The constraint</h4><p>Keep shipping product features throughout migration.</p></section>
                <section><h4>The method</h4><p>Move incrementally by product surface while YUI and React coexist.</p></section>
              </div>
              <p class="case-ai"><span>AI-assisted?</span>No. Pre-LLM delivery.</p>
            </div>
            <p class="case-ai"><span>AI-assisted?</span>No. Pre-LLM delivery.</p>
          </article>
          </details>
        </li>
      </ol>
      <div class="timeline-controls">
        <button class="timeline-toggle" type="button" data-timeline-toggle aria-controls="migration-timeline" aria-expanded="false" hidden>
          Show 4 more migrations
        </button>
      </div>
    </section>
    <section class="discovery" id="discovery" aria-labelledby="discovery-title">
⋯ 54 unchanged lines
  <script>
    (function () {
      var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      var motionQuery = window.matchMedia
        ? window.matchMedia("(prefers-reduced-motion: reduce)")
        : { matches: false };
      var reduceMotion = motionQuery.matches;
      var timelineToggle = document.querySelector("[data-timeline-toggle]");
      var timelineCases = Array.from(document.querySelectorAll(".migration-case"));
      var priorityCases = timelineCases.filter(function (migration) {
        return migration.dataset.timelinePriority === "true";
      });
      var deferredCases = timelineCases.filter(function (migration) {
        return migration.dataset.timelinePriority !== "true";
      });
      function recordPriorityPositions() {
        var positions = new Map();
        priorityCases.forEach(function (migration) {
          positions.set(migration, migration.getBoundingClientRect().top);
        });
        return positions;
      }
      function animatePriorityShift(positions) {
        if (reduceMotion) return;
        priorityCases.forEach(function (migration) {
          if (typeof migration.animate !== "function") return;
          var previousTop = positions.get(migration);
          var currentTop = migration.getBoundingClientRect().top;
          var offset = previousTop - currentTop;
          if (Math.abs(offset) < 1) return;
          migration.animate(
            [
              { transform: "translateY(" + offset + "px)" },
              { transform: "translateY(0)" }
            ],
            { duration: 560, easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
          );
        });
      }
      function animateDeferredCases() {
        if (reduceMotion) return;
        deferredCases.forEach(function (migration, index) {
          migration.classList.add("is-visible");
          if (typeof migration.animate !== "function") return;
          migration.animate(
            [
              { opacity: 0, transform: "translateY(24px)" },
              { opacity: 1, transform: "translateY(0)" }
            ],
            {
              duration: 520,
              delay: index * 70,
              easing: "cubic-bezier(0.16, 1, 0.3, 1)",
              fill: "both"
            }
          );
        });
      }
      if (timelineToggle && deferredCases.length > 0) {
        deferredCases.forEach(function (migration) {
          migration.hidden = true;
        });
        timelineToggle.hidden = false;
        timelineToggle.addEventListener("click", function () {
          var willExpand = timelineToggle.getAttribute("aria-expanded") !== "true";
          var positions = recordPriorityPositions();
          deferredCases.forEach(function (migration) {
            migration.hidden = !willExpand;
            if (willExpand) {
              migration.classList.add("is-visible");
              return;
            }
            var disclosure = migration.querySelector("details");
            if (disclosure) disclosure.open = false;
          });
          timelineToggle.setAttribute("aria-expanded", String(willExpand));
          timelineToggle.textContent = willExpand
            ? "Show fewer migrations"
            : "Show 4 more migrations";
          animatePriorityShift(positions);
          if (willExpand) animateDeferredCases();
        });
      }
      if (reduceMotion || !("IntersectionObserver" in window)) return;
      document.documentElement.classList.add("has-motion");
⋯ 16 unchanged lines