I’m having a lot of fun developing against botbuilder - the Node.js SDK for the bot framework.
When you’re learning to make bots, you study and build a lot of simple bots that do very little. In this case, it makes good sense to simply define the bot’s dialogs in the same file where you do everything else - the file you may call server.js or app.js or index.js. But if you are working on a bot with enough complexity or bulk to the dialogs, you’ll want to settle on a pattern.
Notice in the following code that the main
app.js file configures the dialog by requiring it and then calling the returned function passing in the
bot object. That allows the dialog to use the
bot internally (even though it’s a separate module) to call
bot.dialog() and define the dialog functions.
The result is a much more concise
app.js file and a bit of welcome encapsulation. The dialogs handle themselves and nothing more.
Later, while I was working with Johnson and Johnson on a bot, I developed a pattern for dynamically loading dialogs based simply on a) their presence in the
dialogs project folder and b) their conformation to a simple pattern.
To create a new dialog, then, here’s all I need to do…
getFileNames function is my own, but it simply reads the path you pass in recursively returning all
.map() calls require on the path of each found file and adds the resulting export (in our case here the modules are exporting a function) to the array as a property called
Finally, we call
.forEach() on this and actually execute the function. This configures the dialog for our bot.
The overall result then is the ability to add dialogs to the bot without any wiring. You just create a new dialog, give it a filename that makes good sense in your application, and it should be loaded and ready to be targeted.
You may not get enough context from these snippets to implement this if that’s what you want to do, so check out a fuller sample in the botstarter repo that @danielegan is working on. The botstarter repo is designed to be a good starting point for creating bots.
There are two types of gateways in the IoT (Internet of Things) world.
The first is a field gateway. It’s called such because it resides in the “field” - that is it’s on location and not in the cloud. It’s in the factory or on the robot for instance. Microsoft has an open source codebase for field gateways called the Azure IoT Gateway SDK you can start with.
The second is a cloud gateway, and obviously that one is in the cloud. Microsoft has a codebase for one common cloud gateway function - protocol adaptation available at Azure IoT Protocol Gateway.
Both of these entities exist as a point of communication through which you direct your IoT traffic messages for various reasons.
You’ll also hear the term edge to refer to devices and gateways in the field. The edge is the part of an IoT solution that’s touching the actual things. In the internet of cows, it’s the device hanging on the cow’s collar. In an airliner, it’s all the stuff on the plane itself (which I realize is a confusing scenario since technically those devices may also be in a cloud).
Some possible reasons gateways exist are…
you need to filter the data. It may be that qualifying data deserves the trip to the cloud, but the rest just needs to be archived to local mass storage or even completely ignored.
you need to aggregate the data. Your messages may be too granular, and what you really want to send to the cloud is a moving average, a batch of each 1000 messages, a batch of messages every hour, or something else.
you need to react to your data quickly. It doesn’t usually take that long to get to the cloud and back, but then again “long” is relative. If you’re trying to apply the brakes in a vehicle every millisecond counts.
you need to control costs. You can use filtering or aggregation to massage your messages before going to the cloud to reduce your costs, but there may be some other business logic you van apply to the same end.
you have some cross cutting concerns such as message logging, authorization, or security that a gateway can facilitate or enforce.
you need some additional capabilities. Devices that are not IP capable and able to encrypt messages are dependent on a field gateway to get any messages to the cloud. Devices that are able to speak securely to the cloud but are not for some reason capable to using one of the standard IoT protocols (HTTP, AMQP, or MQTT) require either a field gateway or a cloud gateway (such as Azure IoT Protocol Gateway).
What kind of hardware might you end up using for a gateway? Well, the possibilities are very broad. It could be anything from a Raspberry Pi to a very expensive, dedicated gateway system.
Also, Azure maintains a big catalog of certified hardware including gateways that might be the most helpful resource.
There’s certainly a lot more about gateways to know, but I’ll leave this here now in case it helps you out.
PREAMBLE: I am trying to blog about the little things now. The idea is partly the reason why so many technical blogs exist - it’s a place for me to record things I’ll need to recall later. But modern search engines are good enough, that you just might make it to this blog post to answer a question that’s burning a hole in your brain right now and that’s awesome. I know I love it when I get a simple, concise, and sensible explanation of something I’m trying to figure out.
MORE PRE-RAMBLE: So, I’ve sort of drifted into bot territory. That is, I didn’t initially get extremely excited about the concept of chat bots. It seemed silly. I have since been convinced of their big business value and have really enjoyed learning how to embrace the Node.js SDK for Microsoft’s Bot Framework.
Recently, I realized that the very best way to learn about the SDK is not to search online for docs or posts, but to go straight to the source, and when you get there, look specificallly for the /core/lib/botbuilder.d.ts file.
That file is a treasure trove of useful comments directly decorating the methods, interfaces, and properties of your bot. It’s great that the bot is written in TypeScript, because that means this source code contains a lot of documenting types that not only made it easier for the team to developer this, but now make it easier for us to read it as well.
Tonight I was specifically wondering about something. I had seen middleware components for bots using property values of
send, but then I saw
receive and wondered what every possible property was and specifically what they did.
I discovered that in fact
receive are the only possible property values there. Let me drop that snippet of the source code here, so you can see how well documented those are…
That means that at design time (when you’re typing the code in your IDE), you get good information back about whether what you’re typing lines up with what you said this object is expected to be.
So that’s just one little thing I learned tonight wrapped up with all kinds of preamble, pre-ramble, and other words. Hope it helps. Happy hacking.
I’ve been reading and studying a bit about blockchain recently and am extremely intrigued.
And now, I’ll attempt to explain what blockchain is in brief. Not because you might need to know, but because I usually try to explain something when I’m trying to understand it myself.
Blockchain is a structure and algorithm for storing and accessing data such that data records (transactions or blocks) are hashed and strung together link a linked list data structure. The linking achieved by each block in the chain including as a part of its definition (and thus as a part of its hash) the hash of the previous block in the chain.
So I guess it’s like carrying a little piece (granted a uniquely identifying piece) of history - the hash - along in every transaction.
It’s not unwieldy because each block on has to worry about one extra hash - a tiny piece of data actually.
The benefit, though, is that if a bad guy goes back and modifies one of the records in an attempt to give himself an advantage of some kind in the data, he invalidates every subsequent block.
Because this chain of data is entirely valid or entirely invalid, it is easy for a big group of people to share the entire thing (or even just the last record since it is known to be valid) and all agree on every single change to it.
I watched a TED Talk on the subject and hearing Don Tapscott provide some potential applications of a blockchain really helped to solidify my understanding.
One of his examples was these companies like Uber and Airbnb, which are supposedly highly decentralized and peer-to-peer. They’re not really though, because you still end up with a single company in the middle acting not only as the app developer and facilitator, but more importantly acting as the central source not only all business logic, but also all data and all trust.
In my current understanding, were blockchain to be implemented today in peer-to-peer businesses like these, it would not spell the end of a company like Airbnb. Rather it would mean that their role would be reduced to that of a service provider and not a trust bank. Each stay would be a transaction between a traveler and a host as it is today, but the exchange of dollars and (arguably as important) the exchange of reputation would be direct transactions between two parties.
In addition to a basic blockchain where static values are the content of each block, you have Etherium. This Canadian organization has devised a construct that apparently instead of building up a sort of database of blocks, it builds up a virtual computer. Their website describes it well calling it a decentralized platform that runs smart contracts: applications that run exactly as programmed without any possibility of downtime, censorship, fraud, or third party interference. As I understand it currently, it’s as if each block is not just static data, but rather logic. It can be used to creates rules such as “the 3rd day of each month I transfer $30 to party B”. Check out this reddit post for some well worded explanations on Etherium.
If you have an Azure account, you can already play with Etherium and some other blockchain providers. That’s exactly what I’m doing :)
Comment below if you’re playing with this and want to help me and others come to understand it better.
I worked together with a few fine folks from my team on a very fun hackathon project, and I want to tell you about it.
Here’s our team…
The hackathon was themed on some relatively new products - namely Cognitive Services and the Bot Framework. Additionally, some members of the team were looking for some opportunity to fine tune their Azure Functions skills, so we went looking for an idea that included them all.
I’ve been mulling around the idea of using some of these technologies to implement an escape room, which as you may know are very popular nowadays. If you haven’t played an escape room, perhaps you want to find one nearby and give it a try. An escape room is essentially a physical room that you and a few friends enter and are tasked with exiting in a set amount of time.
Exiting, however, is a puzzle.
Our escape room project is called Cabin Escape and the setting is an airplane cabin.
Players start the game standing in a dark room with a loud, annoying siren and a flashing light. The setting makes it obvious that the plane has just crash landed and the players’ job is to get out.
Players look around in haste, motivated by the siren, and discover a computer terminal. The terminal has some basic information on the screen that introduces itself as CAI (pronounced like kite without the t) - the Central Airplane Intelligence system.
A couple of queries to CAI about her capabilites reveal that the setting is in the future and that she is capable of understanding natural language. And as it turns out, the first task of silencing the alarm is simply a matter of asking CAI to silence the alarm.
What the players don’t know is that the ultimate goal is to discover that the door will not open until the passenger manifest is “validated,” and CAI will not validate the manifest until all passengers are in their assigned seats. The only problem is that passengers don’t know what their assigned seats are.
The task then becomes a matter of finding all of the hidden boarding passes that associate passengers with their seats. Once the last boarding pass is located and the last passenger takes his seat, cameras installed in the seat backs finish reporting to the system that the passenger manifest is validated and the exit door opens.
Architectures of old were almost invariably n-tiered. We software developers are all intimately familiar with that pattern. Times they are a changing! An n-tier monolithic architecture may accomplish your feat, but it won’t scale in a modern cloud very well.
The architecture for cabin escape uses a smattering of platform offerings. What does that mean? It means that we’re not going to ask Azure to give us one or more computers and then install our code on them. Instead, we’re going to compose our application out of a number of higher level services that are each indepedent of one another.
Let’s take a look at an overall architecture diagram.
In Azure, we’re using stateless and serverless Azure Functions for business logic. This is quite a paradigm shift from classic web services are often implemented as an API.
API’s map to nodes (servers) and whether you see it or not, when your application is running, you are effectively renting that node.
Functions, on the other hand do not conceptually map to servers. Obviously, they are still running on servers, but you don’t have to be in the business of declaring how Functions’ nodes scale up and down. They handle that implicitly. You also don’t pay for nodes when your function is not actually executing.
The difficult part in my opinion is the conceptual change where with a serverless architecture, your business logic is split out into multiple functions. At first it’s jarring. Eventually, though you start to understand why it’s advantagous.
If any given chunk of business logic ends up being triggered a lot for some reason and some other chunk doesn’t, then dividing those chunks of logic in different functions allows one to scale while the other doesn’t.
It also starts to feel good from an encapsulation stand point.
Besides Functions, our diagram contains a DocumentDB database for state, a bot using the Bot Framework, LUIS for natural language recognition, and some IoT devices installed in the plane - some of which use cameras.
The camera module is developed with Microsoft Cognitive Services, Azure functions, Node.js, and Typescript. In the module, it performs face training, face detection, identification, and as well as notification to Azure function service. The module determines if the right person is seated or not, then the notification will send back to Azure function service and then the controller decides the further action.
The following digrams describes the interaction between the Azure fuctions services, Microsoft cognitive services, Node server prcessiong and client.
We use Azure Functions to update and retrieve the state of the game. Our Azure Functions are stateless, however we keep the state of every game stored in DocumentDB. In our design, every Cabin Escape room has its own document in the state collection. For the purpose of this project, we have one escape room that has id ‘officialGameState’.
We started by creating a ‘GameState’ DocumentDB database, and then creating a ‘state’ collection. This is pretty straight forward to do in the Azure portal. Keep in mind you’ll need a DocumentDB account created before you create the database and collection.
After setting up the database, we wrote our Azure Functions. We have five functions used to update the game state, communicate with the interactive console (Central Airplane Intelligence - Cai for short), and control the various systems in the plane.Azure Functions can be triggered in various ways, ranging from timed triggers to blob triggers. Our trigger based functions were either HTTP or timer based. Along with triggers, Azure Function can accept various inputs and outputs configured as bindings. Below are the functions in our cabinescape function application.
- GamePulse: * Retrieves the state of the plane alarm, exit door, smoke, overhead bins and sends commands to a raspberry piece * Triggerd by a timer * Inputs from DocumentDB - Environment: * Updates the state of oxygen and pressure * Triggered by a timer * Inputs from DocumentDB * Outputs to DocumentDB - SeatPlayer: * Checks to see if every player is in their seat * Triggerd by HTTP request * Inputs from DocumentDB * Outputs to DocumentDB and HTTP response - StartGame: * Initializes the state of a new game * Triggered by HTTP request * Outputs to DocumentDB and HTTP response - State: * Update the state of the game * Triggered by HTTP request * Inputs from DocumentDB * Ouputs to DocumentDB
A limitation we encountered with timer based triggers is the inability to turn them on or off at will. Our timer based functions are on by default, and are triggerd based on an interval (defined with a cron expression).
In reality, a game is not being played 24/7. Ideally, we want the timer based functions triggered on when a game starts, and continue on an time interval until the game end condition is met.
An escape room is really just a ton of digital flags all over the room that either inquire or assert the binary value of some feature.
- Is the lavatory door open (inquire)
- Turn the smoke machine on (assert)
- Is the HVAC switch in the cockpit on? (inquire)
- Turn the HVAC on (assert)
It’s quite simply a set of inputs and outputs, and their coordination is a logic problem.
All of these logic bits, however, have to exist in real life - what I like to call meat space, and that’s the job of the controller. It’s one thing to change a digital flag saying that the door should be open, but it’s quite another to actually open a door.
The contoller in our solution is a Raspberry Pi 3 with a Node.js that does two things: 1) it reads and writes theh logical values of the GPIO pins and 2) it dynamically creates an API for all of those flags so they can be manipulated from our solution in the cloud.
To scope this project to a 3-day hackathon, the various outputs are going to be represented with LEDs instead of real motors and stuff. It’s meat space, but just barely. It does give everyone a visual on what’s going on in the fictional airplane.
This is a unique “Escape the Room” concept in that it requires a mixture of physical clues in the real world and virtual interaction with a bot. For example, when the team first enters the room, the plane has just “crashed” so there is an alarm beeping. This is pretty annoying, so people are highly motivated to figure out how to turn it off quickly. A lone console is at the front of the airplane, and the players can interact with it.
One of the biggest issues with bots is discoverability: how to figure out what the bot can do. Therefore, good bot design is to greet the user with some examples of what the bot can accomplish. In our case, the bot is able to respond to many different types of questions, which are mapped to our LUIS intents:
- What is the plane’s current status overall?
- How do I fix the HVAC system?
- What is the flight attendant code?
- How do I get out of here?
- How do I unlock the cockpit door?
- How do I open the overhead bins?
- How do we clear all this smoke?
- What is the oxygen level?
- How do I disable the alarm?
The bot (named “CAI” for Central Airplane Intelligence) was implemented in C# using LUIS. The code repository can be found at https://github.com/cabinescape/EscapeTheAirplaneBot.
I’m a sailor. I don’t sail much right now, because I don’t have time, but I still identify as a sailor.
One of the best things about sailing is the marina community. Folks on the docks are amazingly helpful and friendly. You rarely come in to dock without other boaters offering to grab a line and help you land.
There’s good community in software development too. Sure, there are some bad eggs, but overall, the development community is strong. When I meet new developers trying to learn or new graduates trying to land a job, I always recommend they find a few local meetups to visit. Then pick one or two and attend every month.
Before long, they’re rubbing shoulders with other developers, learning things, figuring out what the web front-end flavor of the week is, and even finding new job roles.
Boise, Idaho has a strong developer community in the Boise .NET Developers User Group (NETDUG).
I fly out to Boise now and then to teach and learn from these fine folks who are fortunate enough to live and work in a beautiful area of the country while still working software development jobs.
Last night I talked to the group about the Microsoft Bot Framework in a session I called Bring on the Bots.
As I told the group, I was none too excited about bots when they were first mentioned at Build 2016, but I’ve since seen the future and realized that digitizing conversation is going to be a big part.
If you’re anywhere near Boise, Idaho in March, do know that the Boise Code Camp is a real blast and pretty much a requisite for coders of all breed.
There’s also a Visual Studio 2017 Launch Party the morning of March 9 at the Microsoft office that you’re all welcome too. Hurry because space is limited.
First, in the main project, make sure the
/scaffolds/draft.md is the way you like it. Here’s mine…
Notice how the date is excluded. If you’re writing a draft post, you don’t know when you’ll publish it yet, so you likely want to leave that off. It gets populated automatically a little later. Read on.
Then make sure your
/scaffolds/post.md is the way you like it. Here’s mine…
It looks just the same, but there’s a date there.
Now here’s what my workflow looks like every time I create a new blog post.
- At the command line type
hexo new draft foo
- Switch to Visual Studio Code (or your markdown editor of choice)
- Edit the
foo.mdfile that should be in your
- Back at the command line type
hexo publish foo
When you created the initial draft, it created in the
_drafts folder which doesn’t get generated, so it’s not going to make it to your website yet.
When you published it, hexo moved the markdown file from
_drafts over to
_posts and added the current date and time.
I think that’s a slick workflow and I’m very happy with it.
I just finished migrating all of the content on codefoster.com over to my fifth blog engine!
Hexo, unlike the others, is a static site generator. That means that the work of building pages out of content is done as a build step before the site gets deployed, and the deployed site is actually just a bunch of HTML files. That makes it fast, secure, and searchable. You may have heard of Jekyll - a quite popular site generator that works much the same.
When I first heard about static site generators some time ago, I was actually quite skeptical, but the idea started attracting me more and more. Then when I discovered that Hexo uses Node.js, I decided to give it a shot. I love node.
Hexo was easy to get started with. I went to hexo.io and started learning about all of its capabilities to make sure it would cover my needs, which are…
Need 1: Easy Authoring. I was never as big a fan of Windows Live Writer as so many others were. It just felt like too much behind-the-scenes magic happening. In a static site generator, you author in markdown. A markdown file is a simple text file that uses simple codes for formatting instead of HTML markup which is rather robust. For example, instead of using
<b>strong type!</b>to bold a word, you use
**strong type!**. Furthermore, instead of a table looking like…<table><thead><td>H1</td><td>H2</td></thead><tbody><tr><td>A1</td><td>A2</td></tr><tr><td>B1</td><td>B2</td></tr></tbody></table>
It looks like this…H1 | H2--- | ---A1 | A2B1 | B2
The best markdown when you write on developer topics like I do is the backtick (`). If you use single backticks inline like `let x = 10;`, you get inline text that’s formatted like code like
let var x = 10;. If you surround an entire code block with three backticks, you get an entire code block, and if you add a language code (js, csharp, html, etc.) after the opening backticks, you even get correct color syntax for that language.
Markdown inherently permits inline HTML. It neither complains nor modifies it. Same with embedded
<style>blocks or references to
Need 3: Themes and Plugins. I like web design, but I like it more when it’s done for me and I can just tweak it to my liking. There are a lot of really good themes for Hexo. I also like being able to extend the functionality of my site quickly and easily with plugins. Hexo has plugins too.
I am using the chan theme for codefoster.com right now, but of course it would be trivial to switch.
Installing themes and plugins is as simple as a familiar
git clonecommand. You pull down the files from GitHub, you may need to add a little bit of configuration in your site’s
_config.ymlfile, and you’re good to go.
Need 3: Alias support. I think there’s a lot of value in a good, short URL. The URL for this post, for instance, is simply
codefoster.com/movetohexoas opposed to a URL that a lot of blogging platforms will default to that might be more like
codefoster.com/2016/12/14/Other/Move-to-Hexo. That’s easier to remember, easier to share, easier to type, and easier to look at.
Since, like I mentioned, I’ve changed blog platforms 4 times, I have a number of legacy URLs that are important to maintain. Some of the platforms forced the longer format. Sometimes I just didn’t know how to configure it to be shorter. Regardless, I need to redirect people. I need to redirect them from the old slug to the new.
I also need to redirect folks from short URLs in my domain to external websites. For instance, if you go to codefoster.com/codechat/codegalaxies, you’ll jump out of my domain to Channel 9 to watch an interview I did there.
Hexo allows both of these redirects with easy configuration. This functionality is available, actually, thanks to a plugin called hexo-generator-alias.
Need 4: Local Authoring and Local Serving. I want to be able to work on blog posts whether I’m connected to internet or not, and I want to be able to visualize the results quickly and easily.
Hexo allows me to run the
servercommand to create a local server for my files in watch mode, so I can see the changes in my browser very shortly after making them in my IDE.
All in all, I’m thrilled with my choice of platform and am looking forward to figuring out more of the capabilities.
Drop a comment in the thread below (just opened up guest commenting by the way) if you have feedback, if you have another blog engine that you use and love, or certainly if you’re using Hexo too and want to share some advice.
Someone pointed out that it would be good to share how I migrated my content from my old blog to Hexo. Great idea.
If you browse to hexo.io/plugins and search for “migrate” you’ll see that there are migrators for:
- Blogger (
- GitHub Issues (
- Joomla (
- RSS (
- Wordpress (
I was using a little-known, ASP.NET-powered blog engine called BetterCMS that was actually quite wonderful, although I think I made it clear that I wanted something lighter. There’s obviously not a migrator for BetterCMS, but I did have an RSS feed, so I used that.
I was beyond pleased with the results. I was actually a bit shocked at how simple it was to run and how effective it was in migrating the majority of my content to markdown.
To migrate using the RSS migrator (and I assume the process with the other migrators is much the same), I simply ran at the command line from the root directory of my hexo site
npm install hexo-migrator-rss and then
hexo migrate rss http://codefoster.com/rss. I didn’t use the optional
--alias argument, but if it works as designed it would have been a good idea, because I spent a considerable amount of time doing it manually afterward. The
--alias argument is supposed to add
alias: tags to the top of each post that allows existing blog URLs to be redirected to their new URL.
There was quite a bit of work to do in my markdown files after migration, but all of it was very much expected. It surrounded my code blocks with backticks, but I had to determine where I wanted inline code and where I wanted to use three backticks and a language designation to get a code block. I also discovered that the language designations are rather important since without them, the tool that formats your code either has to format it as generic code (fixed width font) or spend considerable cycles attempting to detect the language.
When you’re using Git for your version control, each commit should be atomic and topical. That is, it should contain related changes and nothing but related changes.
You don’t commit something broad in scope like “changed all the necessary files to implement feature 712”. That’s what a branch is for. Instead, you commit something like “added fetch() method call to the orders page”. That’s likely just one small part of feature 712. A whole bunch of commits end up implementing the feature.
But what about when you’re working away like crazy on your code base and you end up changing a single file in two different places and these two changes relate to different commits? Most people just go ahead and roll the changes into the same commit. Not ideal.
The hard way to do this right is to delete one change, stage and commit, and then paste the change back in.
There’s an easier way though. It’s called a patch add, but I like to call it a partial add.
git add -h will show you the
-p argument and inform you that it allows you to “select hunks interactively”. As much as that sounds like an online dating service for women, its actually just a really easy way from the command line to stage portions of a file and not the entire file.
Let’s say we start with this file…
Now let’s say I end up editing both of the functions in that file, but these changes are unrelated to one another. I simply changed
foo foo and
bar bar. Let’s look first at using the command line to take care of business here, and then we’ll try it with Visual Studio Code.
Here’s the changed file contents…
Now to actually start the staging command, we type
git add foobar.js -p and get the same diff along with these interactive options…
There are actually a few more options than what are listed there too. You can type
?<enter> to get help on what those mean, but to spare time they are…
|y||stage this hunk|
|n||do not stage this hunk|
|q||quit; do not stage this hunk or any of the remaining ones|
|a||stage this hunk and all later hunks in the file|
|d||do not stage this hunk or any of the later hunks in the file|
|g||select a hunk to go to|
|/||search for a hunk matching the given regex|
|j||leave this hunk undecided, see next undecided hunk|
|J||leave this hunk undecided, see next hunk|
|k||leave this hunk undecided, see previous undecided hunk|
|K||leave this hunk undecided, see previous hunk|
|s||split the current hunk into smaller hunks|
|e||manually edit the current hunk|
Which to choose? Well, the diff that we see on the screen shows both changes. That’s too much. So we want to press
s to split this hunk. That gives us…
…where now only one of the changes remains and we have our same interactive prompt.
The change we’re looking at is entirely related and should be in a single commit (along with possibly some other files). So we press
y to stage it and then
q to quit and we’re finished.
Now let’s do the same thing using Visual Studio Code. This and a few other git-enabled IDE’s are smart enough to let you do a patch add. VS Code doesn’t call it a patch add though. It calls it staging selected lines, which actually makes good sense.
Start with the same file and changes, and view the changed file in VS Code and you should see…
Now just put your cursor anywhere within the first change in the pane on the right and then find the little ellipse (
...) on the top right corner of the window. From there choose Stage Selected Lines.
And then if you look in the Git panel, you’ll see that the
foobar.js file has been staged but it also has changes that have not yet been staged.
Whether you used the command line or Visual Studio Code to stage that first change and commit, you can just go about staging and committing the rest of the file as you normally would.
I just opened up guest commenting on codefoster.com.
I’m generally in favor of identity on the web and generally not a huge fan of anonymity. Pretty much every anonymous forum I’ve been to online is littered with the darkest parts of mankind. It’s a sad state really.
So consider this an experiment to see if anonymity will encourage good community on codefoster.com and if manual comment moderation is feasible and sustainable.
We’ll see. Please comment :)