Braving Bash, Part 1

In which I face my fears and finally decide to learn what’s happening in ~/.bash_profile and ~/.bashrc.

At some point in early grade school, after I had exhausted my Jetpack attention span and my ideas for Hypercard-based animated art, I decided I would explore all of the settings I could find on my parents’ Macintosh SE. I was feeling very knowledgable, right up until the moment I stumbled onto the Sad Mac:

Sad Mac

Cue panic.

My dad assures me “the sad Mac was way more common in those days—the MacOS was easier to break,” but even so: since then, I’ve been a bit squeamish about messing around under the hood of my computers.

And then I decided to learn how to code, which—once you make it past online tutorials like Code Academy and Free Code Camp—almost always starts, in my experience, with being told to run a bunch of commands in Terminal and/or paste a bunch of code into your ~/.bash_profile and/or ~/.bashrc files. If you’re lucky, these instructions come with explanations, but often, you have to take it on faith.

I’ve done my best to ignore the creeping feelings of unease and the shades of decades-old Sad Mac panic for several years, dutifully following the instructions and watching these files grow more and more bloated.

When I started at GA, we were given around 20 lines of code to paste into the two files. The next time I opened up Terminal, I saw this:

Last login: Tue Sep 27 11:39:11 on ttys003
You have new mail.
[1;90m
—————————-
Loaded ~/.bashrcTo edit run $ bashedit
To refresh run $ bashrefresh

You are: Rebekah
You’re in: /Users/Rebekah

All aliases…$ alias
—————————-
[1;90m
—————————-
Loaded ~/.bashrc

To edit run $ bashedit
To refresh run $ bashrefresh

You are: Rebekah
You’re in: /Users/Rebekah

All aliases…$ alias
—————————-
~ $

That’s not a copy and paste error: I was getting TWO COPIES of a welcome message I didn’t remember setting but had mostly ignored for several years because everything else seemed to function well enough.

Two copies. Of a message I can’t remember asking for. That starts with 1;90m, which, for all I knew, could be CRITICALLY IMPORTANT SUPER SEKRIT BASH CODE or, you know, a typo.

That's enough!

It was time to fix this.

Step One: Actually look at the code

My approach to all things bash-related for as long as I can remember has been to open up the file(s) using nano, paste in whatever I’m told to paste in wherever I’m told to paste it (usually at the very top or very bottom), save, and close out as quickly as possibly to avoid breaking things.

Pro tip: this is not a good way to understand how things work.

This time, I copied the contents of each file to a separate file so I could “safely” open them up, annotate them, and experiment without—I hoped—bricking my laptop.*

Here’s my ~/.bashrc:

And here’s my ~/.bash_profile:

WAT

Step Two: Ask questions

Now that I had code to look at, I could start googling.

First up: what’s the difference between these two files? Stack Exchange says:

.bash_profile is executed for login shells, while .bashrc is executed for interactive non-login shells.

When you login (type username and password) via console, either sitting at the machine, or remotely via ssh: .bash_profile is executed to configure your shell before the initial command prompt.

But, if you’ve already logged into your machine and open a new terminal window (xterm) then .bashrc is executed before the window command prompt. .bashrc is also run when you start a new bash instance by typing /bin/bash in a terminal.

Cool. I log into my machine every time I restart it or wake it up, and I started this whole journey because of what I was seeing when I opened a new Terminal window. Sounds like I should be looking at .bashrc then. Oh, but wait—there’s more:

if you add the following to your .bash_profile, you can then move everything into your .bashrc file so as to consolidate everything into one place instead of two:

if [ -f $HOME/.bashrc ]; then
  source $HOME/.bashrc
fi

Interesting. I have something pretty similar in lines 6-8 of my .bash_profile:

It’s the same code, minus the $HOME part of the file path. Some more googling leads me to The Linux Documentation Project’s Bash Guide for Beginners, which tells me that -f will be “True if FILE exists and is a regular file.” source means run the code in the file.

To sum up: if a .bashrc file exists in the $HOME directory (in my own .bash_profile, this is written as ~/.bashrc, which means the same thing), then run all of the code it contains.

Okay! Now we’re getting somewhere, sort of: I know that I have two files, and I know that, given how they’re currently set up, they’re both executing when I open a window in Terminal.

Next up

In Part 2: what is all that executable code doing?

Notes and questions

* Is this a thing that’s even possible to do from ~/.bash_profile? It’s been a fear of mine for years. If you know the answer (or can share a good resource), please let me know.

Resources

The Linux Documentation Project’s Bash Guide for Beginners, by Machtelt Garrels

General Assembly WDI, Week 5

Rails is actually a pretty good distraction from the first presidential debate-slash-impending fall of America.

(Apologies in advance if this is more scattered than usual—trying to distract myself from the most painful parts of the first Clinton/Trump presidential debate by blogging.)

Prework

We had some fairly significant studying to do over the weekend, so I’m splitting it into its own section.

The SOLID Principles

We read about SOLID design principles, starting with “Design Principles and Design Patterns” by Robert C. Martin. Delving into these five principles helped scratch some of the itch I’ve been feeling since handing in my Tic-Tac-Toe project: sometimes jumping in and hacking bits off of a large project and tackling whatever I can tackle in order to break the problem down and get it done is exhilarating. Piecemeal != coherent, though, and it’s nice to know there are established guidelines for thinking about a more orderly approach.

The basic gist: bad design is caused by bad dependencies. It’s rigid (difficult to change), fragile (one change causes multiple breaks), immobile (code can’t be easily reused), and viscous (optimal changes are too hard and take too long; it’s easier and faster to circumvent the system). Good object-oriented design uses “dependency firewalls” to help avoid these problems.

  • Single Responsibility Principle: Classes should only do one thing.
  • Open Closed Principle: “A module should be open for extension but closed for modification.” Write modules that can be extended without having to modify them. Rely on abstraction: don’t hard code in a bunch of if/else or switch statements that lock down specific behaviors within a module.
  • Liskov Substitution Principle: “Subclasses should be substitutable for their base classes.” Subclasses should honor the “contracts” of their base classes—they shouldn’t have stronger preconditions (i.e., they shouldn’t need additional things to be true before they’re called), and they shouldn’t have weaker postconditions (i.e., they shouldn’t guarantee that fewer things are true after they’re called).
  • Interface Segregation Principle: Don’t overload a class with methods that all its clients don’t need. It’s better to have multiple small, specific client interfaces than a single large, general interface. Separate methods into different interfaces, and inherit only those that are necessary into a class.
  • Dependency Inversion Principle: “Depend upon Abstractions. Do not depend upon concretions.” High-level things shouldn’t rely on mid-level things that rely on detailed things. Instead, everything should rely on abstract things, which are less likely to break or be changed.

Sandi Metz’s rules

Sandi Metz is a developer and author who specializes in object-oriented programming. Her four rules for developers are focused on keeping things clean and modular in order to help adhere to the SOLID principles. The rules are:

  1. Class shouldn’t be >100 lines
  2. Method shouldn’t be > 5 lines
  3. Methods shouldn’t take >4 parameters (each hash option is 1 parameter)
  4. Controllers can instantiate only one object. Views can only know about one instance variable, and views should only send messages to that object.

Day 20: Code Retreat

Monday gave us a chance to put some of these principles into practice: after a quick discussion on modules (the basics: classes in Ruby can only inherit from one other class. Using modules (mixins) lets us pull in different sets of behaviors/methods and make them available to the class), we spent the rest of the day on a single problem: Conway’s Game of Life. The twist: every 15-45 minutes, we had to wipe the slate clean and start over with a different partner and a different set of constraints. These were the rules:

  • “caveman coder”: We were only allowed a dry erase market and a section of whiteboard.
  • “navigator-driver”: one person types, the other person directs
  • “silent”: can only communicate through comments in the code
  • “flat files”: write methods so things aren’t overly nested
  • “sandi’s rules”: no classes > 100 lines, no methods > 5 lines, etc.
  • “many to one”: one driver, everyone else in the class is navigating
  • “git happens”: we were forced to run git reset --hard HEAD at frequent, random intervals
  • “hot potato”: in a team of four, switch who’s working on the code every 5 minutes

This was both incredibly fun and incredibly frustrating. My takeaways: I like to start with constraints: the board size, the edge case cells. After watching a screencast on using TDD to approach Conway’s Game of Life and talking to others in class, I realized that some people prefer to start with smallest unconstrained piece, ignore the constraints as long as possible, and build from there. This is a totally foreign way of thinking for me, and I’m still struggling to wrap my head around it—even a week later, as I try to process it, my brain is shrieking “BUT…. BUT…. BUT!!!!” Being exposed to this way of thinking and having to pair program with people who tackle problems this way was good for me, though, and I’m hoping to bring a bit more of this kind of thinking into my work, if only as a lightweight experiment at first, and see how it affects my code.

Day 21

Ruby on Rails logo

I’m SO EXCITED to start learning Rails. I’ve used (and am TA-ing for a class on) Laravel, and every time I tell anyone that, I feel like the response is always about how much better Rails is. A few points of comparison so far:

  • The list of ‘uncountables’ in Laravel has long been one of my favorite things (see, e.g., bison, coreopsis, emoji, moose, plankton, pokemon, swine). To be honest, I’m finding the Rails list (in total: equipment information rice money species series fish sheep jeans police) to be sadly lacking.
  • The Rails doctrine is pretty fantastic. I 100% support the idea that “constraints liberate even the most able minds.”
  • Rails gives you the ability to set up example data (used for testing) separately from seed data (necessary for your app to function). This division doesn’t exist by default in Laravel.
  • Serializers let you control what data you pass back (to exclude timestamps or secret/private information, for example). I haven’t used this in Laravel before (it sounds like serializers come into play when you’re building APIs), but the functionality exists.
  • It sounds like I won’t get the chance to compare views in Rails/Laravel—GA only teaches Rails as a way to create APIs (all the front-end work is done as single-page applications).

Before we got too far into Rails, though, we had to learn about PostgreSQL. So far: no real opinions on this vs MySQL, though I am amused by PostgreSQL’s claim to be “the world’s most advanced open source database” compared to MySQL’s “most popular open source database.”

It was good to brush up on my SQL and to play around with psql (a client for working with Postgres), mostly because I can’t wait to start working with data models that I get to design. Front-end is fun, but back-end makes me feel powerful.

Day 22-24

Wednesday kicked off a several-day process of building three different basic APIs: a library API with books and authors, a clinic API with patients and doctors, and a cookbooks API with recipes and ingredients. We learned how to get a Rails app up and running; how to create controllers, models, migrations, and serializers (and how to use scaffold to create all four at once), how to use rails console to interact with our apps through pry, and how to use rails db to interact with the database using psql. A few related notes/tidbits:

  • Sometimes Rails servers don’t close all the way, which prevents you from opening up a new one. To fix: open Activity Monitor and look for spring, ruby, and rails. Kill anything related to those three terms, then try again.
  • Running commands using bundle exec uses the versions you have stored in your gem file instead of global versions.
  • In SQL/Postgres, foreign keys don’t get indices by default. Indices make querying fast. If you’re using an id in a query, write an index. (When you set up references in Rails, indices appear to be automatically created.)
  • SQL Joins: inner joins between tables A and B cover the intersection of data between the two tables. Left joins cover all of table A, plus additional data from table B that meet the join condition. Right joins cover all of table B, plus additional data from table A that meet the join condition. Full joins give you everything.

General Assembly WDI, Week 4

Rubber ducks, Das Sound Machine, and spaceship operators.

Day 15

Monday was project presentation day: we each got five minutes to present our Tic-Tac-Toe games, and two minutes to field questions. I found myself strangely nervous—I was proud of what I had accomplished, but the thought of having to stand up and talk about it, justifying my decisions and explaining my approach, was terrifying.

The order of our presentations was randomized, and I drew one of the last slots, which gave me some time over lunch to open up a couple of different tabs and zoom in on a few things I wanted to talk about in my code—specifically, how I automatically logged users in when they signed up (this Stack Overflow question on passing additional data to AJAX callbacks, which I mentioned in my last weekly write-up, was helpful) and how I checked whether a game had been won or tied. I was proud of the first, and not so proud of the second—I still don’t think a Tic-Tac-Toe game should take 60 lines of code to figure out if it’s over, and near the top of my to-do list is streamlining this.

Taking the time to tee up a browser tab for each of my talking points helped the presentation go smoothly, and at the end, the instructors gave us each a rubber duck, then sent us home early to get some rest. All in all: a good day.

Day 16

Our assignment on Monday night was to walk through a couple of intro to Ruby tutorials and, if we had time, to read Why’s (Poignant) Guide to Ruby. It….

I….

It’s hard to describe.

You should probably read it for yourself.

And then, if it sparks your curiosity as much as it did mine, read Annie Lowrey’s 2009 piece for Slate, “Where’s _why?.”

I read the first half on Monday night and most of the second half on my phone while I was on the train on Tuesday morning, which didn’t in any way do this book justice. I think I need to go back and reread two or three more times at least, preferably on a large screen so I can properly appreciate the sidebars and illustrations.

Jumping into Ruby on Tuesday morning after working with JavaScript for the past year and PHP for several years before that felt like the Barden Bellas felt when they first saw Das Sound Machine.

A few initial notes/impressions (please don’t use these to teach yourself anything about Ruby—this is all Day One knowledge that may or may not be accurate/correct):

  • You don’t need parentheses to call a method without parameters in Ruby. You also don’t need them, even if you’re passing arguments, unless you want to use the results of that call immediately and chain on additional methods.
  • Ruby uses symbols, which, depending on where you read about them, are “lightweight strings,” “a string and a number,” or “the name of the variable, not the value of that variable.” My best shot at describing them: symbols represent a unique spot in memory. They have the same value everywhere in your program. They’re often used in class definitions.
  • Ruby doesn’t have an increment (++) operator. Use += 1 instead.
  • Floats and fixed numbers (integers) are distinct types in Ruby. 7 / 2 will return 3 in Ruby, not 3.5. If you want 3.5, you have to attach .to_f to either the 7 or the 3 before dividing.
  • .to_i truncates, it doesn’t round.
  • Ruby hashes do not allow you to access their keys through a dot notation; you must use square braces.
  • Ruby lets you access values from the end of an array: array[-1] will be the last element in the array.
  • a.equal?(b) will return true if and only if a is the same object in memory as a. Use .eql? to test if the values of a and b are the same.
  • Along the same lines, == is used to test whether values are equal. === is not used to test equality at all, but rather to test for inclusion (is a value in a particular range?). See this Stack Overflow question for details.
  • puts vs p vs print:
    • puts calls .to_s on an object, appends a new line, and prints it.
    • print calls .to_s on an object and prints it without appending a new line.
    • p calls .inspect on an object, appends a new line, and prints it. This can be helpful for debugging because it doesn’t automatically convert everything to a string.
  • Remember my excitement about fat arrow functions in JavaScript? Ruby uses the same symbol (=>) in hash definitions, only it’s called a “hash rocket.”

Day 17

  • More on symbols: Always use symbols as keys when writing hashes. Symbols perform better because Ruby compares them using their object IDs, instead of comparing strings letter by letter.
  • You can search for symbols using Symbolhound. No more frustrating Googling for the Paamayim Nekudotayim!
  • Ruby uses blocks instead of callbacks.
  • In JavaScript, all arguments are optional. JS will try to run the function even if you don’t provide the correct number of arguments. In Ruby, all named arguments are required. In other words: Ruby checks arity (the number of arguments or operands that the function takes); JS does not.
  • The exclamation point after an enumerable method in Ruby means that the method alters the original array.
  • All operators in Ruby are methods. 2 + 2 is the same thing as 2.+(2).

Day 18

We started talking more seriously about enumerables today.

  • Ruby has three basic types of ordered lists: hashes, arrays, and ranges.
  • All of these lists are enumerable, meaning they can take advantage of Ruby’s enumerable methods.
  • In Ruby, hashes have order (in most programming languages, they don’t).
  • In order to use these methods, a class must include the Enumerable module (include Enumerable) and define an each method.
  • Other things—files, for example—are also enumerable. Anything you can iterate over is an enumerable.

We also talked about classes.

  • Ruby is a “classical” language; this means that Ruby uses classes to define and instantiate new objects. JavaScript is a “prototypal” (not prototypical) language; it uses prototypes.
  • Ruby classes have an initialize method that acts like a JS Constructor function. The instance variables set within this method are private by default; “getter” and “setter” methods defined in the class provide access to these variables outside the class definition.
  • Ruby convention is for “getter” and “setter” methods to be named, respectively, propertyName and propertyName=. We can also use attr_reader :varname (getter), attr_writer :varname (setter), and attr_accessor :varname (getter & setter) to create simple versions of these methods.

Day 19

A high-level view of Ruby (made by Artem S.):

Ruby Core Object Model by Artem S.

And a handdrawn version by Jerome Dalbert:

Ruby Core Object Model by Jerome Dalbert

We talked a bit more about enumerables today, and introduced comparables: like enumerables in Ruby, comparables are anything that can be compared, that uses the comparable methods defined in the Comparable module, and that defines a <=> method (aka the “spaceship operator,” apparently so named because it reminded PERL expert Randal L. Schwartz of the spaceship in a Star Trek game).

We also talked briefly about how defining <=> on a comparable class allows you to use the .sort method, which made me wonder what Ruby’s built-in sorting method is. Fun fact: .sort_by in Ruby uses a Schwartzian transform, a sorting algorithm named after the aforementioned Randal L. Schwartz.

A few other notes:

  • Ruby has implicit returns: the last line is always what’s returned.
  • Ruby also has an implicit receiver: you don’t have to use self when you’re calling a getter method on an instance from within a class (though you do have to use it when calling a setter method).
  • super will call the same method defined in the parent or superclass and give you the result.
  • .new is a Class method that calls the Instance method .initialize. .new allocates memory for a new object, creates that object, calls .initialize, and returns the object.
  • self will point to one of three runtime contexts: global context, object context, or class context.
  • In Ruby documentation, # indicates an instance method, while . indicates a class method.
  • def methodname inside a class is how you define an instance method. def self.methodname inside a class is how you define a class method

To sum up

Everyone I know who uses Ruby tells me it’s orders of magnitude better than PHP. I’ve people clap their hands in glee when I told them I’d be learning Ruby as part of GA. I’m hoping to feel some of that excitement soon, but for now:

General Assembly WDI, Week 3: Project Week

Project week! *jumps for joy* *whimpers* *bravely soldiers on*

Squeaking this one in under the wire—the last two weeks have been a whirlwind (I expect I’ll be saying this for a while)!

Last Monday was a holiday. Tuesday was a normal class day. Wednesday through Friday were allocated for work on our first project: a single page Tic-Tac-Toe game built in JavaScript and jQuery, using a Rails API for user authentication and game data storage.

To sum up:

Let’s start at the very beginning, with Tuesday’s class on CSS and SASS:

Day 11

SASS (which GA teaches) and LESS are probably the two best-known CSS preprocessors, but many others exist. We didn’t talk much about the pros and cons of different options, but based on some quick Googling:

  • SASS is written in Ruby; LESS is written in JavaScript.
  • SASS seems to have more available tools to help developers: frameworks like Compass, mixin libraries like Bourbon. LESS has libraries, too, but they’re not quite as powerful and less cohesive.
  • SASS offers selector inheritance via @extend, which is pretty sweet.
  • SASS has better logic than LESS, which is fairly limited.

Fun fact about colors in SASS: the native lighten and darken functions aren’t stellar—they move pretty quickly into complete white/black, rather than tinting or shading things gradually. To fix this, you can use mix to adjust your colors with a bit more control. You can also write custom tint and shade functions that mix colors with white and black to lighten or darken more gradually.

A CSS style best practice I didn’t know before: you should sort your rule declarations by property name in alphabetical order.

Days 12-14: Project Work

The assignment was fairly simple: build a single page JavaScript-based Tic-Tac-Toe game that:

  • renders a game board
  • alternates turns between X and O
  • shows in the browser who’s won at the end of the game, or identifies the game as a tie
  • allows the user to play multiple consecutive games
  • uses AJAX to interact with a provided Rails API to
    • handle authentication
    • store and retrieve game data

My instinct when we got the assignment was to start downloading and playing as many mobile games as possible. I knew I wanted my game to be responsive—who really wants to sit with a laptop and play game after game of Tic-Tac-Toe? It seems like something to do on the train, or while you’re waiting in line for your (much-needed) coffee. Looking at mobile games to identify examples of clean design and intuitive user interaction felt like a good place to start. (The final UI is heavily inspired by the Dots.co family of games, which are among my favorite mobile games for their design, which feels colorful without being overwrought, and their fluidity.)

In hindsight: I wish I’d spent more time thinking about my data models, key functionality, and game logic first. I wrote a handful of user stories and spent a lot of time playing tic-tac-toe and diagramming win conditions, and I didn’t start *coding* the design until I had was able to authenticate users, play games, and log them to the server, but even as I worked on those things, I kept having to stop and reorganize my code to accommodate new features. Taking an hour or two to try to diagram the flow of logic and the exact points at which I needed to interact with the server would have brought more helpful structure to my approach and, I think, kept my code more elegant.

That said: I’m pretty proud of what I built in the span three five days (counting a few hours each on Saturday and Sunday):

Tic-Tac-Toe: large screen log in

Tic-Tac-Toe: mobile game play

Tic-Tac-Toe: mobile game end

Give it a whirl here (and please do let me know if you find any bugs!), or check out the code on GitHub.

Things I learned: I wrote a bit about my adventures in game board design earlier. As I built the functionality, I got some great practice with AJAX (including learning how to pass additional data to AJAX callbacks), and as I styled, I learned how to make a sticky footer. I also learned that it’s shockingly, embarrassingly easy to keep reverting to “x and y” instead of “x and o.” Warning to future tic-tac-toe game developers: this will cause you at least an hour of distress.

Current status: I’m midway through fixing a few small bugs, and I’m still waiting to hear feedback from my instructors at GA—I’m planning to ask them about ways to refactor/restructure and make my logic and flow a bit more elegant.

And my last takeaway: I CAN’T WAIT to learn how to do test-driven development. The single most frustrating part of building this app was having to test each feature manually every time I changed something. I tried to keep careful track of what I needed to test each time, but I *know* I missed things—as evidenced by the remaining bugs in my game—and the process was prone to error (see: manual). I’m not sure if GA teaches TDD explicitly or not, but tests are built into some of the repos they use in training, and I’ve started to track down the frameworks they use (Rspec for Ruby; Mocha and Chai for JavaScript). (In writing this paragraph, I learned about TDD vs BDD. So many things to explore….)

On to week 4 (which is coming to a close today, so expect another recap soon!)!

Ill-Advised Adventures with SVG

In which I am resourceful, if not efficient.

Tl;dr: Don’t do this.

Last week was our first official project week at General Assembly WDI. The challenge: build a tic-tac-toe game in JavaScript that uses AJAX to interact with a Rails API.

I got my basic logic up and running on Wednesday, and on Thursday, I decided to tackle UI. I grabbed a color scheme from an ad for Two Dots, picked out a font, added some nice border radii on a couple of elements, and used a couple of dot-like Font Awesome icons as my game pieces. Overall, I was feeling pretty good about my overall aesthetic:

Tic Tac Toe homepage

And then I took a look at my game board:

unstyled game board

Yeah.

gif-disappointed

It wasn’t time to panic—I could fix this! All I needed to do was to give the board a background, round its edges board, remove the outside border, thicken up those grid lines a bit, and—the final touch—round the edges of the grid lines to better match the overall feel of the game.

I thought about doing this in CSS, but Googling didn’t turn up a way to put round endcaps on border lines in CSS. Rather than wasting time (HA. HA HA HA. This is the part where there’s a freeze frame, and you remember that I said this for the rest of the story for it will prove to be ironic.), I decided to open up Photoshop and make a background image that I could drop into the game board div and be done with it.

Teensy problem: I don’t currently have access Photoshop, a fact I remembered late Thursday night.

I was faced with two options: research open source / low cost graphics programs, acquire one, learn how to use it. Or: further investigate this stroke-linecap attribute that showed up when I was Googling before.

Given that I identify deeply with this joke about Atom, and that it applies to almost every application on my six-year-old laptop, I decided to avoid installing something new. Time to code an SVG element!

I started with this:

See the Pen SVG Game Board v1 by Rebekah Heacock Jones (@rhj) on CodePen.light

Ooooooh, shiny! I was feeling excellent about this until I realized that it was getting later, and I wasn’t sure how to adjust the size of the rectangle and the positions of the lines dynamically at different screen sizes. (I didn’t yet know about ViewBox.)

Solution: pull all the SVG code into a separate file. Apply inline styling. Use the SVG as a background image on your game board div. Go to add/commit, and realize that the provided template for your project includes SVG files in its .gitignore, which you’re too afraid to overwrite because you don’t know why that choice was made in the first place. Export to PNG using SVGtoPNG.com. Since you’re now using PNG, which doesn’t scale well, make a bigger version of the SVG to use on larger screen sizes, and export that.

Stop awkwardly in the middle when you accidentally do this:

Finally end up using image files like you always intended, only you’ve taken the VERY LONG WAY there.

What’s the point of this blog post? It’s part cautionary tale—be careful not to get so attached to an aesthetic that you end up spending hours on a single, unimportant element of a project (unless, of course, that’s actually your job). There are better ways to do this: use SVG correctly and write the code that makes it scale. Ask your instructor (or your boss) why they don’t want SVG files in your git repo, and see if you can work out a better solution together.

I’m also writing this because it’s a good reminder to myself that I can be resourceful. I had a goal, and I achieved it with the tools at my disposal. And now it’s part of a functional public Tic Tac Toe app that anyone—even you?—can use! All in all: a good day.

Some fun side notes: while writing this, I ended up using git checkout [commit hash] to (temporarily) go back to an earlier version of my code, pre-svg, so I could grab screenshots. The Atlassian tutorial on Reset, Checkout, and Revert has an excellent explanation of how to do this.