← Connor Linehan
Systems & Logistics Case Study

Engineering Under Constraint

A real problem from my own life, broken down the way a systems-engineering textbook would: need, measures of effectiveness, boundary, operations, and traceable requirements. Then the SysML model of what I built, and the scheduling theory behind 150 hours of solo iOS development.

Project
NoRush (iOS)
Hours logged
150
Span
Jun 2024 – Present
Drawn by
Connor Linehan
01 — Problem statement

I'm a student, first in high school and now in college, and my schedule rarely lets up. I often end the week burnt out and tired, and my screen time makes it worse. Scrolling social media turns into comparison, and the hours I'm supposed to be off, actually resting, stop feeling like rest.

I also want to practice my faith better: to pray without ceasing and to live more peacefully. When I look at Jesus's life, I see someone who set boundaries and made time to rest, and who still served thousands of people. I want to pursue what I care about with more tenacity and vigor, and I want that work to bear fruit. Resting well is how I get there.

But it's 2026, and my phone is always in my pocket. I don't always remember to bring a book. The damage happens in the small moments, like riding in a car or waiting for class to start, when I pull out my phone without thinking and undo my dopamine patterns.

Need statement, written solution-neutral

A busy student needs a way to turn the unplanned, idle moments of the day into real rest and prayer instead of comparison-driven scrolling. It has to work with a phone that is always present and can't depend on willpower or on remembering to bring anything.

02 — Needs analysis

Systems-engineering texts (Blanchard & Fabrycky, the INCOSE handbook) start by separating the need from any solution. Then they ask how you'd know it was met (measures of effectiveness) and what limits the answer (constraints). Here is that breakdown, and every requirement in section 05 traces back to it.

IDStakeholder needRoot cause it addresses
N1Rest that feels like rest during time that's already "off"Feeds fill downtime with comparison, not recovery
N2Cover the micro-moments (car rides, the minutes before class)The phone is the only thing always in reach, and it defaults to a feed
N3A steady rhythm of prayer and Scripture across a full scheduleFaith practice gets pushed out when there's no set time or trigger for it
N4Rest with other people, not in isolationBoundaries are easier to keep when your circle keeps them too
N5Low friction: no willpower, no cost, nothing extra to carryPlans that depend on discipline fail on exactly the tired days
MOEMeasure of effectiveness
MOE-1Idle moments spent resting or praying instead of scrolling
MOE-2Consistency: consecutive days rested (streak)
MOE-3Exposure to comparison: no public metrics, no endless feed
MOE-4Adoption: whether people keep opening it (rating, retention)
CConstraint
C1The phone stays in the pocket, so the design must work with it
C2One developer, ~150 hours, alongside a full course load
C3iOS platform rules and App Store review
C4Free: a paywall would defeat the point
03 — Context diagram: the system boundary

Before designing what's inside the box, define what's outside it. NoRush doesn't control the schedule, the social apps, or the phone. It can only change what happens at the interfaces between them.

flowchart LR
  U(["Student\n(primary user)"])
  CI(["Circle\n(friends & family)"])
  SCH(["Daily schedule\ncar rides · class gaps"])
  SM(["Social media apps\ncompeting for attention"])
  IOS(["iPhone / iOS\nnotifications · Live Activities"])
  SYS["NoRush\n— system of interest —\nRest Call · verse · prayer · games"]
  SCH -->|idle moment| U
  SM -.->|pull to scroll| U
  U -->|opens instead| SYS
  SYS -->|scheduled push| IOS
  IOS -->|surprise prompt| U
  SYS -->|shared rest| CI
  style SYS fill:#141b26,stroke:#5fc3d4,stroke-width:2px
    
04 — Concept of operations: waiting for class

A ConOps describes the system through a real scenario from the user's point of view. Below is the same five minutes, as-is and to-be.

As-is
  1. Five minutes before lecture, the phone comes out by habit
  2. Instagram opens; the feed has no end
  3. Comparison replaces rest
  4. Class starts, and I'm more drained than before the break
To-be
  1. Same five minutes, same habit, same phone
  2. NoRush opens to today's verse, a quote, and a short checklist
  3. A prayer, a 60-second meditation, or a daily game, and then it's done
  4. Once a day a surprise Rest Call stops the whole circle for seven minutes
05 — Requirements traceability

Each "shall" statement traces back to a need and forward to the feature that meets it, plus a verification method (Inspection, Analysis, Demonstration, or Test). If a feature traces to no need, it's scope creep.

ReqThe system shall…Traces toDesign elementVerify
R1prompt a rest once per day, at an unpredictable time, from one global schedule, never user-initiatedN1 N5Rest Call; timezone-aware scheduled Cloud FunctionTest
R2bound each rest to seven minutes and one photoN1 MOE-3Capture window (state machine, §07)Demonstration
R3present no algorithmic or endless feedN1 MOE-3Circle-only feedInspection
R4fit any gap in the day: either spend 30 seconds or 30 minutes on the appN2 N3Daily verse & quote, prayers, 60-sec meditation, daily gamesDemonstration
R5share rest only within a user-chosen circleN4Circles; Firestore security rulesTest
R6track consecutive days restedN3 MOE-2Streaks & friend leaderboardDemonstration
R7provide every feature at no costN5 C4No paywall, no subscription tierInspection
06 — System structure, modeled in SysML

After NoRush shipped, I modeled it as a SysML block model: a digital twin of the real system in diagram form, so I could reason about its architecture and see how the pieces actually fit. I used the notation from Magic Systems of Systems Architect, the MBSE tool from my coursework, applied to a product that was already live.

Package Diagram — organization
%%{init: {'theme':'base', 'themeVariables': {
  'background':'#10151f','primaryColor':'#141b26','primaryTextColor':'#e8eef5',
  'primaryBorderColor':'#a98af0','lineColor':'#a98af0','secondaryColor':'#141b26',
  'tertiaryColor':'#141b26','fontFamily':'Space Mono, monospace','fontSize':'12px'
}}}%%
flowchart TB
  subgraph CLIENT["«package» Client"]
    direction TB
    V["SwiftUI Views"] --> VM["MVVM ViewModels"]
  end
  subgraph SOCIAL["«package» Social Platform"]
    direction TB
    CI["Circle"] --> FR["Friends"]
    FR --> FE["Feed"]
  end
  subgraph ENGINE["«package» Rest Engine"]
    direction TB
    SC["Scheduling"] --> NO["Notifications"]
    NO --> LA["Live Activities"]
  end
  subgraph DATA["«package» Data & Identity"]
    direction TB
    AU["Auth"] --> FS["Firestore"]
    FS --> ST["Cloud Storage"]
  end
  CLIENT -->|depends on| SOCIAL
  CLIENT -->|depends on| ENGINE
  SOCIAL -->|depends on| DATA
  ENGINE -->|depends on| DATA

  style CLIENT fill:#141b26,stroke:#a98af0,stroke-width:2px
  style SOCIAL fill:#141b26,stroke:#5fc3d4,stroke-width:2px
  style ENGINE fill:#141b26,stroke:#e2a33d,stroke-width:2px
  style DATA fill:#141b26,stroke:#5fd88f,stroke-width:2px
    
Block Definition Diagram — structure
%%{init: {'theme':'base', 'themeVariables': {
  'background':'#10151f','primaryColor':'#141b26','primaryTextColor':'#e8eef5',
  'primaryBorderColor':'#5fc3d4','lineColor':'#5fc3d4','secondaryColor':'#141b26',
  'tertiaryColor':'#141b26','fontFamily':'Space Mono, monospace','fontSize':'12px'
}}}%%
classDiagram
  class NoRushApp {
    <<block>>
    +version : Build
    +startSession()
  }
  class RestEngine {
    <<block>>
    +scheduledTime : Time
    +fireNotification()
    +startLiveActivity()
  }
  class SocialPlatform {
    <<block>>
    +circleId : String
    +postToFeed()
    +listenRealtime()
  }
  class BackendServices {
    <<block>>
    +schemaVersion : SchemaVariant
    +runCloudFunction()
  }
  NoRushApp *-- RestEngine : composes
  NoRushApp *-- SocialPlatform : composes
  RestEngine o-- BackendServices : uses
  SocialPlatform o-- BackendServices : uses
    
07 — System behavior, modeled in SysML

The structure above only says what the system is made of. Below is its behavior counterpart — one RestSession, modeled from scheduled trigger to archived record.

State Machine Diagram — behavior
%%{init: {'theme':'base', 'themeVariables': {
  'background':'#10151f','primaryColor':'#141b26','primaryTextColor':'#e8eef5',
  'primaryBorderColor':'#5fd88f','lineColor':'#5fd88f','secondaryColor':'#141b26',
  'tertiaryColor':'#141b26','fontFamily':'Space Mono, monospace','fontSize':'12px'
}}}%%
stateDiagram-v2
  [*] --> Idle
  Idle --> Scheduled : daily timer set
  Scheduled --> Notified : surprise trigger fires
  Notified --> Capturing : user opens app
  Capturing --> Shared : photo taken [circle confirms]
  Capturing --> Notified : timeout, no action
  Shared --> Archived : 7-minute window closes
  Archived --> [*]
    
Part II — Building it under constraint

I'm an Industrial & Systems Engineering student at Virginia Tech. Here are the two things a classroom never really teaches you: real requirements are discovered, not given — and an hour of work is worth a different amount depending on when you spend it. Both showed up while I was building this, and I had to engineer around them, not just survive them.

All models are wrong, some are useful.— George Box
08 — Why this wasn't a classroom problem
%%{init: {'theme':'base', 'themeVariables': {
  'background':'#10151f','primaryColor':'#141b26','primaryTextColor':'#e8eef5',
  'primaryBorderColor':'#1f2a38','lineColor':'#5fc3d4','secondaryColor':'#141b26',
  'tertiaryColor':'#141b26','fontFamily':'Space Mono, monospace','fontSize':'12px'
}}}%%
flowchart TB
  subgraph CLASSROOM["CLASSROOM PROBLEM"]
    direction TB
    C1["Given: complete, stated inputs"] --> C2["Exactly one correct answer"]
    C2 --> C3["Time bounded by the exam clock"]
    C3 --> C4["Graded on correctness"]
  end
  subgraph REAL["REAL-WORLD PROBLEM: NoRush"]
    direction TB
    R1["Given: incomplete — name and scope\nonly revealed through real usage"] --> R2["No single correct answer —\nrebranded twice in 16 days"]
    R2 --> R3["Time bounded by App Store review\nand my own finals calendar"]
    R3 --> R4["Graded on whether anyone\nkeeps opening it"]
  end
    
09 — Critical chain, not a timeline
%%{init: {'theme':'base', 'themeVariables': {
  'background':'#10151f','primaryColor':'#141b26','primaryTextColor':'#e8eef5',
  'primaryBorderColor':'#1f2a38','lineColor':'#5fc3d4','secondaryColor':'#141b26',
  'tertiaryColor':'#141b26','fontFamily':'Space Mono, monospace','fontSize':'12px'
}}}%%
flowchart LR
  N["Need identified\nJun 2024"] -->|"18 mo self-taught\nWIP=1, no deadline"| P["Low-fidelity prototyping\nLovable + Claude-assisted"]
  P -->|"Finals end —\nhard deadline appears"| B["Build sprint\n~150 hrs total"]
  B -->|"Self-imposed buffer:\n'get submission 90% right today'"| S["Submit + TestFlight\nV&V gate"]
  S -->|"Live usage surfaces\nunknown requirements"| Ch["Requirements churn:\n2 renames in 16 days"]
  Ch -->|"MTTR cut by\ndaily dogfooding"| Op["Operate & sustain\nongoing"]
  Op -.->|"feedback loop"| Ch
    
10 — Concepts applied
Little's Law

L = λW, at a team of one

I was the whole system, so WIP and cycle time moved together for me: holding three half-built features open at once (higher L) stretches every one of their finish times (W), even though my throughput (λ) hadn't changed. Finishing the circle/rest-call feature before starting trivia — rather than running both half-open — was a deliberate choice I made to limit WIP, not a coincidence of order.

Queueing & Utilization

Why 100% effort ≠ 100% output

Queueing theory says wait time explodes as you push utilization toward 100% — a system running flat-out has no slack left to absorb anything unexpected. I lived that curve: my highest-output hour wasn't the one where I'd crammed the most work around it — it was the one where I actually had room to think.

Critical Chain (Goldratt)

The buffer was self-imposed

Instead of padding every task, I put a single project buffer in front of the real deadline: an in-app checklist item I wrote on June 24, 2026 literally read "review and get the next App Store submission 90% right today" — six days ahead of the actual rebrand ship date. That's a project buffer I built into my own product before I'd even studied the term for it.

Requirements Volatility

Two renames were data, not failure

Sabbath → Linger → NoRush in 16 days wasn't scope creep — it was the requirement ("what should this be called and feel like") becoming knowable only once real people in my circle used it daily. Treating that churn as expected and schedulable, instead of a sign something went wrong, is what kept me from blowing the launch date.

Context-Switching Cost

18 months of split attention

Human-factors research consistently finds a real re-orientation cost every time your attention switches tasks. Building solo alongside a full course load for a year and a half meant every time I came back to the codebase, I paid that tax first — I learned the hard way why the May 2026 sprint needed to be one protected, single-purpose block instead of scattered sessions.

Technical Debt as Deferred Cost

The project is still named Sabbath.io

Internally, my Xcode project, bundle identifiers, and folder structure are still literally named for the app's first name — I renamed the product twice, but never touched the substrate underneath. That's a maintenance cost I deferred on purpose: safe to postpone, expensive to fix mid-sprint, the same tradeoff any engineer makes under a live deadline.

Version & Variant Constraint

One system, three names, one schema contract

Sabbath, Linger, and NoRush are really the same underlying system wearing three different names — what a systems engineer would call a variant. But my backend evolved on its own version axis too: I ran a Firestore schema migration alongside one of the rebrands, so I needed a real constraint holding the two together: { clientBuild ≥ 2.7.1 ⇒ backendSchema = v3 }. If I'd shipped a client without honoring that constraint, I'd have broken every circle already live on the old schema — which is exactly why I ran the migration before the client update, not after.

11 — Resume bullets
01

Independently designed, built, and shipped a native iOS app over 150 hours, applying a full systems-engineering lifecycle — needs identification, low-fidelity prototyping, TestFlight-based verification, and phased deployment — to take a solo concept to a live, 5.0-rated App Store launch.

02

Modeled the shipped system in SysML — package, block definition, and state-machine diagrams built in Magic Systems of Systems Architect — to formally capture structure and behavior for a product that already existed in production, not as pre-build documentation.

03

Diagnosed requirements volatility from live usage data and responded by executing two full product rebrands within 16 days, rather than treating the churn as project failure — kept the original launch timeline intact.

04

Applied WIP-limiting (Little's Law) to a single-person development pipeline, sequencing feature delivery to hold cycle time flat as scope grew across three shipped engagement features.

05

Built a self-imposed critical-chain buffer ahead of a hard external deadline (App Store review), scheduling internal readiness checkpoints days before the real delivery date to absorb schedule risk proactively.

06

Ran continuous dogfooding as a production feedback loop, personally using the app daily to surface and resolve a live reliability bug the same day it appeared.

07

Made an explicit cost-of-delay tradeoff to defer non-critical technical debt (legacy internal naming) in favor of shipping velocity — a deferral, not an oversight, made under real resource constraints.