WAP phones I administered
Carrier-side Nokia handsets, WML decks, and a browser window the size of a postage stamp. Mobile web before anyone called it that.
The Nokia on my desk had a browser window the size of a postage stamp. I did not own the phone. I administered it. Carrier configuration, device profiles, deck URLs that had to resolve before billing started counting kilobytes. The handset was a managed object on my side of the contract. The person carrying it heard a ringtone and maybe checked weather once a week.
WAP arrived in the late nineties promising a web you could carry in a pocket. What landed on my bench was WML, Wireless Markup Language, decks of cards instead of pages, and latency that made dial-up look cooperative. Each card was one screen. Tap forward, wait, read four lines, wait again. The radio billed by the kilobyte on many plans, so every deck load was a line item someone would audit.
I learned WML because I could not administer a stack I could not read. Same rule I applied to LAMP boxes. If the markup lived in a repository I controlled, I could diff it, roll it back, and tell operations why Tuesday’s deck push broke login. If it lived only on a vendor portal behind credentials I did not hold, I did not own the service. I owned the outage ticket when the gateway returned a blank card.
What the constraints actually were
Headlines had to survive in a handful of characters if you wanted them visible without scroll. The viewport left no room for decoration. I will say that again in the language I used on the bench. A title that looked finished in a desktop browser wrapped onto a second line on the Nokia, and that second line pushed the first link off the card. If the first link left the screen, a tired person never tapped it.
Images cost real money per byte on several billing plans, so most production decks were text and links. A WBMP I approved in the emulator still became a line item once the radio carried it. I stopped shipping pictures unless operations could name the audit row they would defend.
High latency meant you could not assume the user would tap again. Every wait was a chance to pocket the phone and forget what they had started. A card that asked for a password after a long pause lost the person who had already typed a username. I treated that pause as a drop, the same way I treated a PHP page that sat on a query until the browser gave up.
The Nokia toolkit on my workstation rendered that same stamp-sized viewport in an emulator. I loaded a deck and watched it fail, then fixed the WML before I pushed again. The loop was slow. It was also inspectable. I trusted it more than any floor demo I saw at a trade show.
I will put the toolkit in the language I still use. The emulator drew the card the handset would draw, including the wrap I could not see in a text editor. It compiled the deck the way the gateway would compile it, and it told me when that compile blew the limit the device profile allowed. A floor demo hid that number behind a scripted happy path.
A request still had to cross a WAP gateway after it left my content server. The server spoke HTTP and served WML. The gateway compiled that markup into something the radio would carry. When the card came back blank, I had to learn whether the gateway had dropped the payload.
A gateway fault taught you nothing. The screen said the service was unavailable, or it said nothing at all. I opened the deck in the toolkit and asked whether the markup still compiled. That question was the whole job on a bad morning. If the file compiled, I still had to learn whether the gateway had fetched the URL I thought it had.
Take a weather deck I used to keep live. The content server returned a valid WML file when I curled it from the bench. The toolkit loaded that file and drew four lines of forecast. The live handset showed a blank card. The gateway log had compiled a cached copy from Monday, and Tuesday’s URL still pointed at a card id I had renamed. The emulator showed the broken fragment. The cache still served Monday.
I spent the next hour proving that to operations with two files on the same desk. Monday’s deck still had card id="forecast". Tuesday’s file had renamed it to card id="today" and left one href="#forecast" in the index card. The toolkit caught the broken fragment when I stepped through the cards. The gateway had never fetched Tuesday’s file. It had served the compiled Monday deck and then failed the tap that asked for a card the old compile no longer contained.
Device profiles made that class of failure louder. A firmware build I had not seen in the lab would wrap a line the emulator left alone, or it would refuse a deck the toolkit had compiled under an older profile. I kept the profile next to the deck in the same repository. If I could not name the profile the handset was actually using, I was guessing, and guessing is how a weather card turns into a ticket.
A deck that broke on Tuesday
The login deck was two cards. The first asked for a username and posted it forward. The second asked for a password and posted both fields to a script on a box I administered. The script looked like the PHP I already ran on LAMP work. It read the post and returned the next card after it checked a table.
I will say the failure in the language I used on the ticket. Tuesday’s push added a remember-this-handset card between the password and the welcome screen. In the text editor the new card looked harmless. In the compiled deck it pushed the login over the size the profile allowed. The gateway dropped the deck and returned a blank card. People who opened mail on those handsets could not sign in. Billing still counted the failed fetch.
The markup lived in a repository I controlled that week, so I could diff the push. The new card added a postfield the script did not read, and it omitted the username field the password card had been forwarding. Even if the compiled size had fit, the script would have seen a password and no user. I rolled the deck back to Monday’s file and told operations the blank card was ours.
I have watched the other version of that outage. The same login lived only on a vendor portal. Someone with a password I did not hold pasted a change into a form and published it. I could not diff that paste or roll it back. I owned the ticket until they found a person who still remembered the portal login.
That is why I learned WML the same way I later moved mysql_* calls to mysqli, and then to PDO. I wanted the failure in a place I could read. A password mixed into a query string in an include is the same class of problem as a username dropped between cards. You cannot administer a hop you cannot open.
On the LAMP box that object was a catalog user I could GRANT and a statement I could log. A DSN lived in one place I could lock down, and a broken statement failed in a way I could print.
On the WAP bench the matching object was a deck file I could compile and a gateway log I could grep. I wanted both on the same desk before I told anyone the login was safe. Both jobs ended with a sentence I could defend to the person holding the outage.
The rollback that Tuesday was a single file. I checked out Monday’s deck and compiled it in the toolkit until the two cards posted the username and the password together. The script saw both fields and returned the welcome card. I pushed that file and sat on the gateway log until a live handset drew the same card. The remember-this-handset idea went into a note. It did not go back into the deck until I could keep the compiled size under the profile and keep the username in the post.
What stayed
I have not touched WML in years. The habit remained. When a browser or an assistant cannot show me its configuration file, I assume I will debug it blind.
I will put that habit in the language I use now. If I cannot open the file that produced the screen, I will spend the afternoon guessing which layer moved. WAP taught that on hardware I could hold in one hand. The deck was small enough to read. The blank card was loud enough to remember.
I still keep inspectable software on the boxes I administer. Configs live in a tree I can clone. Open weights sit on that same disk when I want a model I can hash. The reflex is older than that file.
A hosted window can change the thing behind the glass between Tuesday and Wednesday. I learned that when a gateway served Monday’s deck under Tuesday’s URL. I still assume a layer I cannot read will lie about what it returned.
The mysql work trained the same muscle in a language I already spoke. When I could follow a request from the vhost to the query log, I could tell a client why the cart was empty. When I could not, I was holding a login and hoping someone else still paid for the portal.
I apply that walk to a config I can open on hardware I run. I want the path named, and I want a restore I can perform without a vendor ticket. If either step depends on a dashboard that closes when the contract ends, I treat the service as theirs. That sentence is the WAP lesson with the radio turned off.
The rule still asks for a file I can diff on a machine I can name. Those two answers were enough on the Nokia bench, and they are enough on a disk I still administer.
The blank card
People still ask whether those phones counted as mobile web. I administered the decks, so I already know what the radio carried. I still want the file I can compile and the log that names the hop.
I ignore a pitch that wants me to trust a viewport I cannot reproduce on a bench I control. The toolkit was slow and ugly, and it still showed the wrap and the compiled size. A slide that only shows the happy path is the floor demo I already walked past.
I also ignore a mobile backend that will only show me a dashboard. If the configuration lives behind a credential I do not hold, I will get the blank card again. The radio is faster now. The ticket text has not changed.
When the gateway returned nothing, the person carrying the Nokia thought the phone was broken. The deck I pushed was the thing that failed. I still start there. Name the file you shipped, and name the machine that compiled it. If you cannot do both, you are already writing the outage.